Styles | Vale
For the complete documentation index, see llms.txt. This page is also available as Markdown.

Styles

Learn what a rule is, how rules are collected into styles, and how a configuration switches them on.

A rule

A rule is four decisions. Which check runs, which says what kind of thing is wrong: a pattern that must not appear, a word that must be spelled one way, a count that must not be exceeded. Which scope it runs on, which says where to look: every sentence, only the headings, one section of the document. What the message says when it fires. And, when there is one right answer, what the action is that fixes it.

extends: existence
message: "Don't use end punctuation in headings."
scope: heading
nonword: true
tokens:
  - '[a-z][.?!]$'

That is a complete rule. It runs the existence check on every heading and reports the span that matched. The same rule, fully dressed, from the Microsoft style:

extends: existence
message: "Don't use end punctuation in headings."
link: https://learn.microsoft.com/en-us/style-guide/punctuation/periods
nonword: true
level: warning
scope: heading
action:
  name: edit
  params:
    - trim_right
    - '.?!'
tokens:
  - '[a-z][.?!]$'

The link is the guidance the rule enforces, shown with every alert. The level is how loudly to say it. The action is the fix, which an editor or an agent applies without deciding anything. nonword is an argument to the check itself: the pattern ends in punctuation, so the check is told not to wrap it in word boundaries.

The rule's YAML with each key called out: extends is the check, message is what to say, link is the guidance, the next key is an argument to the check, level is how loudly, scope is where to look, action is the fix, and tokens is the pattern.

The header

Every rule opens with the same fields, followed by the arguments its check takes:

Name
Required
Default
Description

extends

Yes

The check the rule runs, or another rule to build on. extends: existence

message

Yes

What to say when the rule fires. Each check offers its own placeholders. message: "Don't use '%s' in headings."

level

No

warning

suggestion, warning, or error. Only errors set a non-zero exit code. level: error

scope

No

text

Where to look. See Scopes. metric and readability default to the whole document instead. scope: heading

link

No

The guidance the rule enforces, shown with the alert. link: https://example.com

description

No

A longer explanation, for outputs and editors that show one. description: "Headings are labels, not sentences."

action

No

The fix. See Actions. action: { name: replace }

limit

No

The most times the rule may fire in one file. limit: 3

matchcase

No

false

Adapt a suggested replacement to the case of the text it replaces. matchcase: true

The checks

Each rule extends a check, which is what does the work. Every check runs on any scope.

Name
Description

Report a pattern that is present.

Report a pattern and carry its replacement.

Count a pattern, and report too few or too many.

Report a pattern that repeats.

Report a file that uses two forms of one thing.

Report a pattern that appears without another it depends on.

Report text that is not cased the way the rule says.

Evaluate a formula over the counts of a block, and report when the condition holds.

Score a block's prose, and report when the grade is too high.

Spell check against Hunspell-compatible dictionaries.

Report a sequence of tokens, with part-of-speech tags, in a sentence.

Run a Tengo program over a block, and report what it returns.

A style

A rule is one file, and a style is a directory of them, kept under the StylesPath:

The directory is the style's name, and the file is the rule's, joined with a dot: Microsoft.HeadingPunctuation is how the rule is addressed in configuration, in an in-text comment, in a filter, and in Vale's output. A subdirectory joins the name too, so House/dates/TimeFormat.yml is House.dates.TimeFormat.

Two things about the files themselves:

  • A rule file ends in .yml. Vale reads no other extension as a rule.

  • A directory whose name starts with . or _ is skipped at load time, so drafts and shared fragments can sit inside a style without loading.

Packages installed with vale sync land on the same path, so a style you wrote and a style you downloaded are the same kind of thing. The config/ directory beside them holds what is not a style: vocabularies, dictionaries, views, filters, and templates.

Switching rules on

A style is enabled by name in BasedOnStyles, and every rule in it runs at the level the rule declares. The configuration can then adjust one rule, or a whole style, without touching the files:

A rule named on its own, House.Hedging = YES, loads even when its style is not in BasedOnStyles: every segment after the style is a path, and Vale reads that one file.

The bracket key takes scalars: a number, a boolean, a level. It refuses the keys that make a rule what it is, message, link, tokens, swap, exceptions, and the rest, because changing those is authoring. For that, extend the rule.

Extending another rule

Rule inheritance requires Vale v3.20.0 or later.

An extends value containing a dot names another rule rather than one of the checks. The new rule starts from that rule's full definition and lays its own keys on top, so two styles can share one carefully built pattern and differ only where they mean to.

The examples below extend these two parents:

The parent only has to exist on the StylesPath; it does not have to be switched on. Inheritance is a file reference, and vale sync is what puts the file there. The built-in Vale rules have no file, so they cannot be extended.

Values

A key the child writes replaces the parent's value. Every other key is inherited as it is:

House.Hedging reports the parent's three phrases as errors, with the new message. Its ignorecase and tokens are the parent's.

This holds for a list or a map too: a bare tokens or swap in the child replaces the parent's wholesale.

House.Basically reports basically and nothing else.

Lists

A list can be edited instead of replaced. tokens+ appends to the parent's list, and tokens- removes from it:

House.Stricter reports somewhat, it seems, arguably, and to some extent. An entry to remove has to match one of the parent's as YAML reads it, so perhaps and 'perhaps' are the same entry.

Any list key takes + and -: exceptions, tokens, raw, and so on.

Maps

A map takes the same two edits. swap+ merges the child's entries into the parent's, and the child's value wins where both have the same key. swap- takes a list of keys to drop:

House.Terms maps e-mail to email, utilize to apply, and sign-in to sign in. It no longer knows web site.

What is an error

Removing something the parent does not have is an error, so an upstream rename is heard about rather than silently diverged from:

Writing both key and key+ (or key-) in one file is an error: that says "replace" and "edit the replacement" at once. Only a rule that extends another rule can use + or -; a rule that extends a check has no parent for them to act on. A chain may run ten rules deep before Vale assumes it is a cycle.

Running the parent too

Extending a rule does not switch the parent off. If the parent's style is also on, both rules run and both report:

Switch the parent off in the configuration when that is not what you want:

Sharing a pattern

A directory whose name starts with _ or . is skipped at load time but stays visible to extends, so a pattern shared by several rules can live in one file without itself becoming a rule:

Every segment of the name after the style is a path component, so the rules refer to the file as GenZ._shared.Slang:

A fragment is validated as the chain's root, so it must carry a message, even one no alert will ever show.

Linting a style

Requires Vale v3.22.0 or later.

A style holds rules and vocabularies, not prose, so a run over a directory skips the StylesPath. Name a file or directory inside it to lint it anyway:

A rule file is then linted by its message and description alone. Its tokens, swap, exceptions, and the rest are patterns, and are never read as prose, so a pattern like optimi[sz]e is not reported as two misspellings.

Patterns

Most rules are patterns. Vale runs them on a regexp2 engine in RE2 mode: Go's regexp/syntax, plus positive and negative lookahead ((?=re), (?!re)) and positive and negative lookbehind ((?<=re), (?<!re)). See the Regex guide for how the checks wrap a pattern before running it.

The built-in style

One style, Vale, is generated at runtime rather than read from disk. Its four rules report as errors:

Name
Description

Vale.Spelling

Spell check, with any Hunspell-compatible dictionaries in <StylesPath>/config/dictionaries and any suggestion the dictionary can make.

Vale.Terms

Enforce the casing of the project's accepted Vocabulary terms.

Vale.Avoid

Report the project's rejected Vocabulary terms.

Vale.Repetition

Report a repeated word, "the the", and carry the fix.

They switch on with BasedOnStyles = Vale, and each can be adjusted from the configuration like any other rule.

Last updated