vale.ini and the custom styles
The enforceable half: configuration and rule definitions.
The enforceable half of the style guide. Each rule carries the defect it
prevents in its message, so the writer who trips it reads the reason rather
than only the correction.
Rationale for every rule, and for the rules deliberately not written, is in the ruleset and why. Two checks in that list are not Vale rules, because Vale lints the text of one file at a time: link checking and required front matter run as separate steps in docs-quality.yml.
Written for Vale 3.
.vale.ini
StylesPath = .vale/styles
MinAlertLevel = warning
# Product names and approved terms, so they are never flagged as spelling
# errors. Vale 3 reads them from .vale/styles/config/vocabularies/Product/.
Vocab = Product
Packages = Google
[*.md]
BasedOnStyles = Vale, Google, Docs
# Google's rules, adjusted where they conflict with a decision recorded in
# the style guide.
# Passive voice is often correct in technical writing. Rejected outright.
Google.Passive = NO
# Docs.FutureTense covers the same ground with a narrower pattern. Running
# both reports every hit twice.
Google.Will = NO
# The help center speaks as the company, and "we" is correct there.
Google.We = NO
Google.FirstPerson = warning
Google.Headings = warning
[docs/reference/**.md]
# Generated from the OpenAPI specification. Only Vale's spelling check runs
# here: a typo in a schema description surfaces on this page first. Everything
# else about this content is governed by the Redocly ruleset that gates the
# specification itself.
BasedOnStyles = Vale
[docs/changelog.md]
# Deprecation notices state future behavior on purpose. "Will be removed" is
# a commitment; the present tense would describe it as already true.
Docs.FutureTense = NO
.vale/styles/config/vocabularies/Product/accept.txt
Orchestrator
Ledger
Sift
[Ii]dempotenc(y|e)
[Ww]ebhook
OpenAPI
Redocly
Vale
[Rr]unbook
.vale/styles/Docs/AmbiguousPronoun.yml
extends: existence
message: >-
"%s" opens this sentence. If the previous sentence names more than one thing
it could refer to, name the noun. A reader who has to guess will guess
wrong about half the time, and in a procedure a wrong guess is a wrong
action.
level: warning
scope: paragraph
nonword: true
tokens:
- '(?<=[.!?]\s)(?:It|This|That|They)\b'
.vale/styles/Docs/DirectionalWords.yml
extends: existence
message: >-
"%s" describes a position on the page, not a target. The reference breaks
on a narrow screen, in a single-topic export, and for a screen reader.
Name what you are pointing at instead.
level: error
ignorecase: true
tokens:
- 'see (above|below)'
- 'the (table|list|diagram|figure) (above|below)'
- 'as shown (on the (right|left)|above|below)'
- 'click here'
- 'see here'
.vale/styles/Docs/ConditionAfterAction.yml
extends: existence
message: >-
This step states its condition after its action. The reader performs the
action before learning it was conditional. Move the condition to the
front: "If the check fails, restart the service."
level: error
scope: list
nonword: true
tokens:
- '^(?!(?:If|When|Unless)\b)[A-Z][a-z]+\b[^.]*?\s(?:if|when|unless)\s'
.vale/styles/Docs/StepHasOneAction.yml
extends: existence
message: >-
This step contains more than one action. Split it. The second action is
skipped when the reader is interrupted between them, and interruption
during a procedure is the normal case.
level: error
scope: list
nonword: true
tokens:
- '^(?:Enter|Select|Run|Open|Copy|Install|Restart|Delete|Add)\b[^.]*\b(?:and then|, then|and)\s+(?:enter|select|run|open|copy|install|restart|delete|add)\b'
.vale/styles/Docs/StepLength.yml
extends: occurrence
message: >-
This list item runs to more than 20 words. A reader performing a procedure
holds the whole step in working memory before acting. Split it.
level: warning
scope: list
max: 20
token: '\b\w+\b'
.vale/styles/Docs/PlaceholderLeft.yml
extends: existence
message: '"%s" is a placeholder, and this page is about to ship with it.'
level: error
tokens:
- 'TODO'
- 'TKTK'
- 'FIXME'
- 'XXX'
- '[Ll]orem ipsum'
- '\[[Ii]nsert [^\]]{0,30}\]'
.vale/styles/Docs/LinkText.yml
extends: existence
message: >-
The link text carries no meaning out of context: "%s". Screen-reader users
navigate by a list of links with the surrounding prose stripped away.
Describe the destination.
level: error
ignorecase: true
scope: raw
nonword: true
tokens:
- '\[(?:here|this|link|more|read more|learn more|click here)\]\('
- '\[https?://[^\]]*\]\('
- '<https?://[^>]+>'
.vale/styles/Docs/AltText.yml
extends: existence
message: >-
This image has no usable alt text. Where a diagram carries information the
prose does not, a screen-reader user loses the page.
level: error
scope: raw
nonword: true
tokens:
- '!\[\s*\]\('
- '!\[.{1,4}\]\('
- '<img(?![^>]*\balt\s*=\s*"[^"]{5,})[^>]*>'
.vale/styles/Docs/TermSubstitutions.yml
extends: substitution
message: >-
Use "%s" rather than "%s". A reader searching for the word on the screen
finds nothing when three articles use three words for one action.
level: warning
ignorecase: true
swap:
'sign in|signin|log on': log in
'sign out|signout|log off': log out
'remove the app': uninstall
'hit (?:the )?enter': press Enter
'tick the box': select the checkbox
'gr[ae]yed[ -]out': unavailable
.vale/styles/Docs/Wordiness.yml
extends: substitution
message: >-
Use "%s" rather than "%s". The longer form adds reading time and no
meaning.
level: warning
ignorecase: true
# "in order to" is not listed: Google.WordListCase already reports it.
swap:
at this point in time: now
is able to: can
prior to: before
make use of: use
for the purpose of: for
.vale/styles/Docs/FutureTense.yml
extends: existence
message: >-
"%s" states system behavior as a prediction. Use the present tense: "the
service starts", not "the service will start". The reader is deciding
whether to depend on this behavior.
level: warning
tokens:
- '\bwill (?:be |then )?(?:start|stop|return|send|create|display|show|appear|remove)\w*'
.vale/styles/Docs/VersionedCommands.yml.tmpl
Vale rules cannot read another file, so this one is generated. The template
is committed; CI substitutes the current release from
docs/_data/versions.yml before Vale runs, and the generated
VersionedCommands.yml is listed in .gitignore.
extends: existence
message: >-
Version %s does not match the current release, __CURRENT__. Version numbers
in prose rot silently: nothing fails until a reader runs the command.
level: error
scope: raw
nonword: true
tokens:
- '(?<=[@=v])(?!__CURRENT_RE__\b)\d+\.\d+\.\d+\b'
scope: raw is deliberate. Version numbers live in code blocks, and Vale skips
code blocks in every other scope.
Suppressing a rule
A rule that is wrong for one specific passage is suppressed inline, with the reason in a comment beside it:
<!-- The label is quoted exactly as the product shows it. -->
<!-- vale Docs.TermSubstitutions = NO -->
Select **Sign in with SSO**.
<!-- vale Docs.TermSubstitutions = YES -->
Suppressions are reviewed quarterly. A cluster around one rule means the rule has false positives, and the fix is the rule, not the suppressions.