Think grammarly, but as a CLI tool.
You can even add it to your CI/CD build.
- Installing vale
- Configuring vale
- Using vale
On my M1 Mac this was as simple as using brew:
brew install vale
This installed the
vale CLI and made it avaialble in my PATH, so I could open a terminal and use it right away:
❯ which vale /opt/homebrew/bin/vale ❯ vale vale - A command-line linter for prose. Usage: vale [options] [input...] vale myfile.md myfile1.md mydir1 vale --output=JSON [input...] Vale is a syntax-aware linter for prose built with speed and extensibility in mind. It supports Markdown, AsciiDoc, reStructuredText, HTML, and more. To get started, you'll need a configuration file (.vale.ini): Example: StylesPath = a/path/to/your/styles MinAlertLevel = suggestion [*] BasedOnStyles = Vale See https://vale.sh for more setup information. (Or use vale --help for a listing of all CLI options.)
vale took me some time as I learned a few things about the tool:
- it requires a configuration file to work (default file is
.vale.iniin whatever directory you are currently in) and this can be set per-project and checked in under source control
- it requires a set of yaml styles to be available on your local machine
- it includes a
syncsubcommand to download styles based on your configuration file
Here is what I recommend:
Visit this vale Config Generator and experiment with the dropdown boxes to generate a
Here is the one I selected for my project:
StylesPath = .styles MinAlertLevel = suggestion Vocab = Base Packages = Google, write-good [*] BasedOnStyles = Vale, Google, write-good
Note that I changed the default
.styles. This means that
valewill look for styles yaml files inside the
.stylesfolder of my project.
Prefixing a file or folder name with a
.means that the file or folder will be hidden from a normal directory listing, similar to the
I made this choice because I don’t want to distract first time contributors with a
stylesfolder that is part of my project’s standards but not part of my project’s content.
Save this file as
.vale.iniin the root of your project folder.
❯ cd my-project ❯ ls .vale.ini .vale.ini ❯ cat .vale.ini StylesPath = .styles MinAlertLevel = suggestion Vocab = Base Packages = Google, write-good [*] BasedOnStyles = Vale, Google, write-good ❯
Open a terminal, change directory into the root of your project folder, and run this
Here’s what it looked like for me with the
❯ cd my-project ❯ vale sync SUCCESS Downloaded package 'Google' SUCCESS Downloaded package 'write-good' Downloading packages [2/2] ❯
This uses the settings in your project’s
.vale.ini to download styles yaml files and put them in your local
.vale.ini file in my project folder that looks like this:
❯ cd my-project ❯ ls . .styles .vale.ini ❯ tree .styles .styles ├── Google │ ├── AMPM.yml │ ├── Acronyms.yml │ ├── Colons.yml │ ├── Contractions.yml │ ├── DateFormat.yml │ ├── Ellipses.yml │ ├── EmDash.yml │ ├── EnDash.yml │ ├── Exclamation.yml │ ├── FirstPerson.yml │ ├── Gender.yml │ ├── GenderBias.yml │ ├── HeadingPunctuation.yml │ ├── Headings.yml │ ├── Latin.yml │ ├── LyHyphens.yml │ ├── OptionalPlurals.yml │ ├── Ordinal.yml │ ├── OxfordComma.yml │ ├── Parens.yml │ ├── Passive.yml │ ├── Periods.yml │ ├── Quotes.yml │ ├── Ranges.yml │ ├── Semicolons.yml │ ├── Slang.yml │ ├── Spacing.yml │ ├── Spelling.yml │ ├── Units.yml │ ├── We.yml │ ├── Will.yml │ ├── WordList.yml │ ├── meta.json │ └── vocab.txt ├── Vocab │ └── Base │ ├── accept.txt │ └── reject.txt └── write-good ├── Cliches.yml ├── E-Prime.yml ├── Illusions.yml ├── Passive.yml ├── README.md ├── So.yml ├── ThereIs.yml ├── TooWordy.yml ├── Weasel.yml └── meta.json 4 directories, 46 files
tree command is a unix command to print out a folder structure as a tree shape. On MacOS the command is available via
brew install tree
I checked in both
.vale.ini and the entire
.styles folder into source control so that anyone who clones or forks this project is using the same style files; there are lots of style files but yaml is pretty compact when stored in
Now that you have the prerequisites installed:
- a valid
- style yaml files downloaded to the
StylesPathconfigured in your
you are ready to use
vale to validate (valedate?) your content.
So far I have only used
vale on Markdown files.
vale on my preject’s README originally looked like this:
❯ vale README.md README.md 5:13 suggestion Spell out 'GPL', if it's Google.Acronyms unfamiliar to the audience. 13:20 warning 'go-git-mob' should use Google.Headings sentence-style capitalization. 16:5 error Did you really mean 'golang'? Vale.Spelling 16:24 error Did you really mean 'nodejs'? Vale.Spelling 33:46 warning 'Table of Contents' should use Google.Headings sentence-style capitalization. 50:4 warning 'About The Project' should use Google.Headings sentence-style capitalization. 52:14 suggestion Try to avoid using 'is'. write-good.E-Prime 52:54 suggestion Feel free to use 'it's' Google.Contractions instead of 'It is'. 52:54 warning 'It is' is too wordy. write-good.TooWordy 52:62 error Did you really mean 'golang'? Vale.Spelling 52:81 error Did you really mean 'nodejs'? Vale.Spelling 52:121 error Did you really mean Vale.Spelling 'complimenents'? 53:52 suggestion Try to avoid using 'be'. write-good.E-Prime 53:52 warning 'be detected' may be passive write-good.Passive voice. Use active voice if you can. 53:52 suggestion In general, use active voice Google.Passive instead of passive voice ('be detected'). 53:186 error Did you really mean 'sesson'? Vale.Spelling 54:53 warning Avoid using 'will'. Google.Will 54:58 suggestion In general, use active voice Google.Passive instead of passive voice ('be squashed'). 54:58 warning 'be squashed' may be passive write-good.Passive voice. Use active voice if you can. 54:58 suggestion Try to avoid using 'be'. write-good.E-Prime 60:8 warning Try to avoid using Google.We first-person plural like 'we'. 60:26 warning Try to avoid using Google.We first-person plural like 'we'. 60:135 suggestion Try to avoid using 'was'. write-good.E-Prime 60:277 warning 'only' is a weasel word! write-good.Weasel 60:285 suggestion In general, use active voice Google.Passive instead of passive voice ('be left'). 60:285 suggestion Try to avoid using 'be'. write-good.E-Prime 60:285 warning 'be left' may be passive write-good.Passive voice. Use active voice if you can. 60:320 warning 'In addition' is too wordy. write-good.TooWordy 60:375 suggestion In general, use active voice Google.Passive instead of passive voice ('was introduced'). 60:375 suggestion Try to avoid using 'was'. write-good.E-Prime 60:375 warning 'was introduced' may be write-good.Passive passive voice. Use active voice if you can. 60:467 suggestion Try to avoid using 'be'. write-good.E-Prime 62:14 suggestion In general, use active voice Google.Passive instead of passive voice ('was created'). 62:14 warning 'was created' may be passive write-good.Passive voice. Use active voice if you can. 62:14 suggestion Try to avoid using 'was'. write-good.E-Prime 62:105 suggestion In general, use active voice Google.Passive instead of passive voice ('be installed'). 62:105 warning 'be installed' may be passive write-good.Passive voice. Use active voice if you can. 62:105 suggestion Try to avoid using 'be'. write-good.E-Prime 62:167 error Did you really mean 'nodejs'? Vale.Spelling 62:205 error Did you really mean 'repo'? Vale.Spelling 64:5 warning 'Built With' should use Google.Headings sentence-style capitalization. 70:4 warning 'Getting Started' should use Google.Headings sentence-style capitalization. 76:8 suggestion Use parentheses judiciously. Google.Parens 76:9 warning Try to avoid using Google.We first-person plural like 'we'. 76:9 suggestion Feel free to use 'we're' Google.Contractions instead of 'we are'. 76:12 suggestion Try to avoid using 'are'. write-good.E-Prime 83:5 warning 'Utility Commands' should use Google.Headings sentence-style capitalization. 85:38 warning 'a number of' is too wordy. write-good.TooWordy 107:105 suggestion Use parentheses judiciously. Google.Parens 128:12 error Did you really mean 'Kotze'? Vale.Spelling 128:27 error Did you really mean 'Ideler'? Vale.Spelling 128:149 error Did you really mean 'nodejs'? Vale.Spelling ✖ 11 errors, 20 warnings and 21 suggestions in 1 file. ❯
Wow that was a lot of feedback. In the end, however, it did not take me very long to work through each piece of feedback and resolve them.
Accepting proper nouns and domain specific language
Some of the errors were spelling errors with unrecognized proper nouns or technology keywords.
I learned that you can add such proper nouns and known words to your local styles files by adding them to a
Here is what mine looks like for the
Github Make make Makefile rbenv nodenv asdf aruba Golang golang nodejs go-git-mob git-mob GPL url Kotze Idele
These words are no longer flagged as
Disabling linting rules with markdwn comments
Another neat trick is that
vale supports disabling and enabling all validation or just specific rules using special comments in the same way that other linters (e.g.
rubocop, etc) do.
Disable one rule
<!-- vale Google.Headings = NO --> <h1 align="center">go-git-mob</h1> <!-- vale Google.Headings = YES -->
Google.Headings validation rule for that one top-level heading, which I do because I know that
go-git-mob is the name of the project.
Disable all validation
Occasionally you may want to disable all validation, such as when including a blockquote.
You don’t want style/linting rules for your project to cause you to misquote someone else.
In this case you can temporarily disable all validation by surrounding your blockquote like this:
<!-- vale off --> <blockquote>...</blockquote> <!-- vale on -->
Set up guard for rapid feedback
I like rapid feedback, so I set up the
guard command-line tool to re-run validation every time my
CONTRIBUTING.md files chagne.
I added these two gems to my project’s
group :development do gem 'guard' gem 'guard-process' end
And these lines to a
guard 'process', :name => 'Valedation', :command => 'vale README.md CONTRIBUTNG.md' do watch('README.md') watch('CONTRIBUTING.md') end
Now I can
cd into my project folder and run
guard to enable this file watching behaviour:
❯ bundle exec guard 14:02:48 - INFO - Starting process Valedation 14:02:48 - INFO - Started process Valedation 14:02:48 - INFO - Guard is now watching at '/Users/dalpert/projects/go-git-mob'  guard(main)> ✔ 0 errors, 0 warnings and 0 suggestions in 2 files.
And any time the
guard tool detects a change to one of those markdown files it will automatically re-run validation:
14:04:38 - INFO - Stopping process Valedation 14:04:38 - INFO - Stopped process Valedation 14:04:38 - INFO - Starting process Valedation 14:04:38 - INFO - Started process Valedation  guard(main)> ✔ 0 errors, 0 warnings and 0 suggestions in 2 files.
This was helpful when resolving the initial linting feedback but also when editing, to have updated linting output every time you save a file.
When resolving the first round of errors I ran
guard in a terminal window inside VSCode and worked on the last errors/warnings first. As I fixed them and saved the file, the
guard script would trigger and update the validation leaving new errors/warnings at the bottom of the list. This let me keep my terminal window small and the editing window large and usually focus on one or two rules at a time.
Add a make target
On this project I am using
Make as a build tool.
Here is the
Make target I created to wrap validation:
vale: ## run linting rules against markdown files vale README.md CONTRIBUTING.md # we don't valedate CHANGELOG.md as that reflects historical commit summaries
Now I can run
make vale to run that validation and use the
vale target as a prerequisite in my
cit (CI build and Test) target to fail a build if any of the markdown validation rules fail.
cit: clean vale build test-unit test-features
Once I get PR builds working on this repo that
vale feedback will be available to all contributors who make markdown changes.
Once I got over the initial setup challenges I found
vale very easy to use.
Similar with other code quality and linting tools I’ve used (I’m looking at you
rubocop) the first time I turned
vale on my project’s
README.md it was easy to get overwhelmed with the amount of feedback.
Now that I’ve worked through it however and having a passing mark on both README and CONTRIBUTING I find it easy to keep that validation on and deal with smaller more targeted feedback on only the content I’m changing.
A number of the initial pieces of feedback were challenging to resolve but eliminating passive voice really does make the content stronger.
I found that with the
accept.txt file to expand my spelling dictionary and a few targeted markdown comment when a rule truely didn’t make sense.
I am now very happy to have
vale running (and passing) against my project’s