Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>Slim Syntax and LintNew to Visual Studio Code? Get it now.
Slim Syntax and Lint

Slim Syntax and Lint

CadenzaTech

| (0) | Free
Slim language support with syntax highlighting, snippets, and linting powered by slim-lint
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info

Slim

Slim

Slim language support with syntax highlighting, snippets, and linting powered by slim-lint.

License Tag Test Lint


Features

  • Syntax highlighting for .slim, including the ruby:, javascript:, css:, sass:, scss:, less:, coffee:, markdown: and erb: filters, --- YAML front matter, and verbatim text
  • Multi-line Ruby, continued with a trailing comma or a trailing backslash
  • #{...} interpolation highlighted as Ruby wherever it appears, including inside filters
  • Diagnostics from slim-lint, with each linter name linking to its documentation
  • Quick Fixes to disable a linter for a block with / slim-lint:disable comments
  • Go to Definition and completion for the partial a render call names
  • Selection refactorings: wrap in a conditional or a Ruby block, and extract to a new partial
  • Completion for the data-* attributes Turbo, Stimulus and Rails UJS define, both bare after the tag and inside () / [] / {} wrappers
  • Snippets for Slim control flow, filters, doctypes and comments
  • 239 Rails view helper snippets (link_to, form_with, f.text_field, turbo_frame_tag, ...), offered only when the file belongs to a Rails project
  • Automatic bundle exec detection, with a fallback to the slim-lint on your PATH
  • No telemetry and no network requests

Requirements

Syntax highlighting and snippets work on their own. Diagnostics need slim-lint:

gem install slim_lint

or add it to your Gemfile:

gem 'slim_lint', require: false

slim-lint 0.22.0 or newer is required: 0.21.0 introduced --stdin-file-path, the flag this extension lints your unsaved buffer through, and 0.22.0 the inline slim-lint:disable comments the Quick Fixes write.

No formatter

This extension deliberately registers no formatter: slim-lint has no auto-correct of any kind, so there is nothing that could format a Slim file without inventing a rewriter of its own — and a formatter that guesses is worse than none. If slim-lint grows an auto-correct, a formatter belongs here too.

The disable-comment Quick Fixes need no subprocess and work today.

Completion and Emmet

VS Code's built-in Emmet counts Slim among the languages it handles, so it is active in .slim files whether or not you want it there. Its suggestions crowd out the word-based suggestions VS Code would otherwise offer from the current file.

If completion feels unhelpful, turn Emmet off for Slim alone:

"emmet.excludeLanguages": ["markdown", "slim"]

Two things to know:

  • Keep "markdown" in the list. It is there by default, and because this setting is an array your value replaces the default rather than adding to it.
  • emmet.showExpandedAbbreviation cannot do this. It is a window-scoped setting, so putting it under "[slim]" has no effect, and setting it to "never" globally would disable Emmet in HTML too. Its "inMarkupAndStylesheetFilesOnly" value does not help either: it only restricts Emmet to the languages it supports natively, and Slim is one of them.

This extension deliberately does not disable Emmet on your behalf, since plenty of people want it.

Rails snippets

On top of the Slim snippets, 239 Rails view helper snippets are available: the full set from haml-vscode — whose = helper and - ... do bodies are valid Slim exactly as they are — plus 18 helpers it predates, such as form_with, turbo_frame_tag, turbo_stream_from, dom_id, rich_text_area and the tag builder. Typing = link and accepting link_to gives you = link_to(...), not = = link_to(...), and f.te completes to f.text_field rather than f.f.text_field.

They are off in a plain Slim project and on in a Rails one, with nothing to configure:

// Look for config/application.rb or a Gemfile.lock listing rails (default)
"slim.snippets.rails": "auto"

// Always offer them
"slim.snippets.rails": "on"

// Never offer them
"slim.snippets.rails": "off"

auto reads the workspace from disk, so it is always off for files that are not on the local filesystem — a virtual workspace such as GitHub Repositories, a diff from the git: scheme, or an untitled buffer. It is also off for a file opened on its own without a workspace folder, since there is then no directory to search upwards from. Set "on" in those cases.

Two behaviours differ from the other built-in Slim snippets, because a contributed snippet file cannot be switched off by a setting and these are therefore supplied by a completion provider instead:

  • they do not appear in the Insert Snippet command
  • they do not expand with editor.tabCompletion

They are suggested as you type like any other snippet, and honour editor.snippetSuggestions: "none".

The Slim control-flow snippets (if, ifelse, unless, each, case, yield, ...) come from the same provider and share those two differences, for a different reason: a contributed snippet replaces only the word you typed, so after a marker it would leave - - if condition behind. Supplied this way they work whether you type if or - if, whatever slim.snippets.rails says, and stay out of filter and text blocks, where if is JavaScript or prose.

They are matched by the start of the word, so that they never stand in the way of the word-based suggestions for a tag you are typing: start content_for with c, not with cf. For the same reason triggering suggestions on an empty line lists none of them — type the first letter.

Partials

Ctrl / Cmd click a partial name in a render call to open it, or use Go to Definition and Peek Definition. Typing inside the quotes completes the names of the partials that exist. There is nothing to configure and no Ruby process is involved — only file names are read, so both work in an untrusted workspace too.

The name is resolved against the app/views directory that contains the current file:

Written Opens
= render 'shared/foo' app/views/shared/_foo.html.slim
= render 'sidebar' _sidebar.html.slim beside the current file, then app/views/application/
= render partial: 'shared/foo' the same as the first form
= render layout: 'shared/foo' do the same as the first form

A name without a slash is a best guess. Rails looks it up under the prefixes of whichever controller renders the view, which a file on its own does not say; beside the current file is where that is for a view in its controller's own directory, so it is tried first.

.slim is preferred over .erb, and the current file's own format over html: from index.turbo_stream.slim, = render 'shared/foo' opens _foo.turbo_stream.slim when it exists and falls back to _foo.html.slim when it does not.

= render template: 'posts/index' is deliberately not followed. A template resolves without the leading underscore, so treating it as a partial would point at a file that is not there.

Completion always inserts the app/views-relative name, also for a partial beside the current file: render 'sidebar' only resolves from a view in the rendering controller's own directory - from shared/ or a layout it is a missing partial - while render 'posts/sidebar' resolves from anywhere. Typing just side still finds it, and until something is typed the partials beside the current file are listed first. Turn it off with:

"slim.completions.partials": false

data attribute completion

Inside an attribute list, typing data- completes the attributes Turbo, Stimulus and Rails UJS define, in both Slim notations:

a data-turbo-frame="modal" Open
div(data-controller="dropdown")

Attributes whose presence is the value — data-turbo-permanent, data-turbo-stream and the rest — are inserted as data-turbo-permanent=true after the tag, where a bare name would read as inline text, and as the bare boolean name inside a wrapper. Stimulus contributes only data-controller and data-action: target, value and class names are per controller, so a placeholder for them would never match what you type.

Nothing is read from disk and no process is started, so this works in an untrusted or virtual workspace. Turn it off with:

"slim.completions.dataAttributes": false

Syntax highlighting only

To get highlighting and snippets without ever starting a Ruby process:

"slim.lint.run": "off"

With that set, nothing is spawned when you open, edit or save a .slim file. The Slim: Lint File command still runs slim-lint, because invoking it explicitly is a deliberate request.

Rails snippets never spawn anything either, but auto does read Gemfile.lock from the workspace. Set "slim.snippets.rails": "off" to stop even that.

Settings

Setting Default Description
slim.lint.run onSave When to run diagnostics: onSave (also on open), onType, or off.
slim.lint.debounceMs 500 Debounce in milliseconds while typing. Only used when slim.lint.run is onType.
slim.lint.exclude [] Glob patterns of files to skip. See Known Limitations.
slim.slimLint.executablePath null Absolute path to the slim-lint executable, or a bare command name resolved on PATH; relative paths are refused. Skips bundler detection when set.
slim.slimLint.useBundler auto Whether to run through bundle exec: auto, always, or never.
slim.slimLint.configPath null Configuration file passed as -c.
slim.slimLint.timeoutMs 15000 How long to wait for a slim-lint process before terminating it. See Known Limitations.
slim.snippets.rails auto Whether to offer Rails view helper snippets: auto, on, or off. See Rails snippets.
slim.completions.partials true Whether to complete partial names inside a render call. See Partials.
slim.completions.dataAttributes true Whether to complete Turbo, Stimulus and Rails UJS data-* attributes. See data attribute completion.

slim.slimLint.executablePath, slim.slimLint.useBundler and slim.slimLint.configPath are machine-scoped, so a repository cannot point them at an arbitrary binary through its own .vscode/settings.json.

Commands

Command Description
Slim: Lint File Run slim-lint against the active file.
Slim: Wrap in Conditional Wrap the selection in - if, with the condition selected so you can type over it.
Slim: Wrap in Block Wrap the selection in a Ruby each block, with the collection selected.
Slim: Split to Partial Move the selection into a new partial and replace it with = render.
Slim: Restart Linter Drop every cached conclusion and re-lint open files.
Slim: Show Output Channel Open the log, which records every command, its working directory, exit code and stderr.

Security

Linting a Slim file runs Ruby code from your workspace: bundle exec evaluates the Gemfile, and .rubocop.yml — which slim-lint's RuboCop linter reads — can require arbitrary .rb files. This extension therefore declares limited support for untrusted workspaces — in a workspace you have not trusted, syntax highlighting and snippets work, and no process is ever spawned.

Known Limitations

  • exclude: in .slim-lint.yml is not applied. Linting from the editor pipes the buffer through --stdin-file-path, which bypasses slim-lint's file finder — the stage that applies the top-level exclude: globs. Use slim.lint.exclude instead: its globs are matched against the path relative to the workspace folder, so an exclude: entry written relative to the project root carries over as it is. Per-linter include: / exclude: are unaffected and still work.
  • Diagnostics cover a whole line. slim-lint reports a line number and no column.
  • A file that times out is left alone until something changes. Every run boots Ruby and RuboCop afresh, so once a run has exceeded slim.slimLint.timeoutMs on a document, saving it again would only spend the same time to be killed again. Automatic runs for that document are therefore paused until it gets smaller, slim.slimLint.timeoutMs is raised, or you run Slim: Lint File or Slim: Restart Linter. The output channel records it when it happens.
  • This extension never writes to your configuration files.
  • Only .slim-lint.yml and .rubocop.yml are watched. Changing either re-lints the Slim files you have open. A configuration reached some other way — a file named by slim.slimLint.configPath, or one pulled in by inherits_from — is still read on every run, but changing it does not refresh anything on its own until you edit a .slim file or run Slim: Lint File.
  • The sass: and erb: filter bodies are only partly highlighted. VS Code has no built-in grammar for indented Sass, so a sass: body stays uncoloured unless a Sass extension is installed. In an erb: body the Ruby inside <% %> tags is highlighted and the markup around them is left plain, with or without an ERB extension: handing the body to an outside HTML grammar let a tag left open while typing colour the rest of the file. The other filters map to scopes the built-in grammars provide.
  • Slim: Split to Partial adds no locals:. Instance variables carry over on their own, but a selection using a block variable needs the argument adding by hand — deriving them means parsing the Ruby in the selection, and getting that wrong would silently change what the view renders. It also never overwrites: if a partial of that name already exists the command stops, and it needs a file saved on disk, unlike the two wrap commands which work in an untitled buffer too.
  • A selection is interpreted by indentation alone. With no selection the block under the cursor is used, and a selection whose last line still has children is extended to include them — otherwise raising it one level would detach them. Nothing understands filters, so wrapping the body of a ruby: or javascript: filter produces broken Ruby or JavaScript, and neither does anything understand multi-line Ruby, so a selection starting midway through a comma- or backslash-continued expression is not valid either.
  • Partials are resolved against one app/views. The one containing the current file, which means an engine's or a dummy app's is used when the file lives there. prepend_view_path and an engine's view path chain would need the application to be booted, so they are not followed.
  • Attribute completion reads one line. A wrapped attribute list spread over several lines cannot be judged from the line being typed, so nothing is offered there.
  • Partial completion needs a workspace folder. File search always comes back empty without one, so a .slim file opened on its own gets Go to Definition but no completion. Multi-line render partial: calls are not covered either, since the name has to be on the line being typed.
  • slim language id conflicts. Several extensions contribute the slim language and the text.slim grammar. If more than one is installed the result is whichever loads last, so installing only one is recommended.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/cadenza-tech/slim. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.

License

The extension is available as open source under the terms of the MIT License.

The bundled TextMate grammar is derived from ruby-slim.tmbundle by the Slim team, and the Rails snippet set from haml-vscode by Karuna Murti — both MIT licensed. See syntaxes/NOTICE.md for the vendored commits and the list of modifications.

Code of Conduct

Everyone interacting in the Slim project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.

Sponsor

You can sponsor this project on GitHub Sponsors.

  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft