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

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.

Sponsor spotlights

These companies support Vale's future — and put it to work in their own products today.

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.md
Markdown parsed before prose rules run
#Installation
You can **utilize** Vale to lint [prose](/docs)—it ignores syntax like `code` and URLs.
>A blockquote can still contain very unique product guidance.
-Install the extension before the end result ships.
Vale is availible for macOS, Windows, and Linux.
```js
const message = "This code utilizes a URL";
```
[^install]: https://example.com/docs/utilize
Checked
headings, prose, quotes, lists
Skipped
syntax, links, code, footnotes
suggestion Consider replacing 'utilize', 'very unique', and 'end result'.
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
person.rs
comments extracted with tree-sitter
/// Creates a person with the given name.
///
/// # Examples
///
/// ```
/// use doc::Person;
/// let person = Person::new("name");
/// ```
pub fn new(name: &str) -> Person {
// Names can utilize any script.
let raw = "// not a comment and not prose";
}
Checked
line comments, doc comments, Markdown prose
Skipped
syntax, identifiers, strings, fenced code
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.