Vale CMS Need more than a rule playground? Explore
Open source · MIT 5.8K

Vale is a linter for prose

A command-line tool that brings code-like linting to your writing. Enforce a consistent editorial style across every doc, in any format—entirely offline.

bash — vale
$ vale sync
SUCCESS Synced 2 package(s) to 'styles'.
$ vale docs/
docs/configure.md
3:7 suggestion Consider using 'use' instead of 'utilize'. Microsoft.Wordiness
3:44 suggestion 'are loaded' looks like passive voice. Microsoft.Passive
9:12 error Use 'Vale CLI' instead of 'Vale cli'. Docs.Terms
docs/install.md
3:40 warning Use 'select' instead of the input-specific verb 'Click'. Microsoft.UIVerbs
4:28 error Did you really mean 'existant'? Vale.Spelling
2 errors, 1 warning and 2 suggestions in 2 files.

Read their configs

Every team here publishes something you can open — the .vale.ini they run, or the page they wrote about running it.

Why Vale

Most tools see text. Vale sees a document.

A heading, a code block, a comment, and a link URL are not the same thing—and a rule that can't tell them apart is a rule you end up switching off.

Markup-aware

Parses your markup instead of guessing at it

Twelve formats, each through a real parser rather than a pattern. Rules can target headings, lists, or table cells—and code spans, URLs, and fenced blocks are skipped before a rule ever runs.

  • Markdown
  • AsciiDoc
  • reStructuredText
  • MDX
  • MyST
  • Quarto
  • Typst
  • HTML
  • XML
  • DITA
  • Org
  • QDoc
How scopes work
# Installation
You can **utilize** Vale to lint [prose](/docs)—it ignores syntax like `code` and URLs.
Vale is availible for macOS, Windows, and Linux.
suggestion Consider using 'use' instead of 'utilize'.
error Did you really mean 'availible'?
Code-aware

Your comments are documentation too

Vale lifts comments out of nineteen languages with tree-sitter grammars, so it knows where a comment ends and a marker inside a string literal stays code. The Markdown inside a doc comment is linted as though it were its own file.

  • Go
  • Rust
  • Python
  • Ruby
  • C++
  • C
  • JavaScript
  • TypeScript
  • TSX
  • Java
  • Haskell
  • Julia
  • Lua
  • PHP
  • R
  • QML
  • Protobuf
  • YAML
  • CSS
Inside the grammar
func Get(id string) (*Record, error) {
// It can utilize the cache when possible.
key := "// not a comment"
}
suggestion Consider using 'use' instead of 'utilize'.
Extensible

Rules that read grammar, not just strings

Part-of-speech patterns, cross-file relationships, readability formulas, and Tengo scripts sit alongside the token lists—twelve extension points in all. Each is a few lines of YAML in a folder you can hand around as a package.

All twelve extension points
sequence Matches grammar, not characters
extends: sequence
message: "Use 'meetup' instead of 'meet up'."
level: error
tokens:
  - upos: NOUN
    pattern: meet
  - pattern: up
Our next meet up is on Tuesday.
Let's meet up on Tuesday.
error Use 'meetup' instead of 'meet up'.
# One alert, not two: 'meet' is a noun in the first sentence and a verb in the second.
conditional One pattern requires another
extends: conditional
message: "'%s' has no definition."
level: error
scope: text
first: '\b([A-Z]{3,5})\b'
second: '(?:\b[A-Z][a-z]+ )+\(([A-Z]{3,5})\)'
exceptions:
  - API
The HTTP endpoint requires an API key.
error 'HTTP' has no definition.
# An acronym is allowed only once its expansion appears somewhere in the file.
consistency The file has a memory
extends: consistency
message: "Inconsistent spelling of '%s'."
level: error
either:
  advisor: adviser
Ask your advisor about it.
Then ask a different adviser.
error Inconsistent spelling of 'adviser'.
# Neither spelling is wrong. Using both in one file is.
metric Arithmetic over the whole file
extends: metric
message: "Grade level (%s) is above 8."
level: warning
formula: |
  (0.39 * (words / sentences)) +
  (11.8 * (syllables / words)) - 15.59
condition: "> 8"
Notwithstanding the aforementioned considerations regarding deployment, the implementation necessitates a comprehensive understanding of the underlying architecture and its associated operational characteristics, which must be evaluated in their entirety prior to the commencement of any migration activity undertaken by the platform engineering team.
warning Grade level (31.41) is above 8.
# Counted, not matched—words, sentences, and syllables across the document.
script Drop down to real code
extends: script
message: "This paragraph runs long—consider splitting it."
level: suggestion
scope: raw
script: |
  text := import("text")
  matches := []
  for line in text.split(scope, "\n") {
    if len(text.split(line, " ")) > 40 {
      i := text.index(scope, line)
      matches = append(matches, {begin: i, end: i + len(line)})
    }
  }
Notwithstanding the aforementioned considerations regarding deployment, the implementation necessitates a comprehensive understanding of the underlying architecture and its associated operational characteristics, which must be evaluated in their entirety prior to the commencement of any migration activity undertaken by the platform engineering team.
suggestion This paragraph runs long—consider splitting it.
# Tengo, compiled once when the rule loads and cloned per block.
Fast

One binary, nothing to install alongside it

Written in Go, with no runtime and files linted in parallel. GitLab runs 82 rules across all 2,827 pages of its documentation in under twenty seconds.

What makes it fast
2,827
pages of Markdown
82
rules applied
<20s
start to finish

Private by design

Nothing leaves your machine. No account, no upload, and no training on your writing.

Runs everywhere

macOS, Windows, and Linux. MIT-licensed and maintained in the open.

Style guides included

Microsoft, Google, Red Hat and thirteen more, each installed with one command.

One tool, every app

More than just a command-line interface.

Vale runs where you already write—in your editor, in your notes app, and in CI before anything merges.

Ready to start linting?
Install now.