Important
This plugin is a community project and is NOT officially supported by Tailwind Labs.
An unofficial Tailwind CSS integration and tooling for Neovim written in Lua and JavaScript, leveraging the built-in LSP client, Treesitter, and the NodeJS plugin host. It is inspired by the official Visual Studio Code extension.
- Features
- Prerequisites
- Installation
- Configuration
- Commands
- Utilities
- Extension
- Related projects
- Contributing
The plugin works with all languages inheriting from html, css and tsx treesitter grammars (php, astro, vue, svelte, ...). Lua patterns can also be used as a fallback.
It currently provides the following features:
- Class color hints
- Class concealing
- Class motions
- Class sorting (without prettier-plugin)
- Completion utilities (using nvim-cmp)
- Class previewer (using telescope.nvim)
Note
Language services like autocompletion, diagnostics and hover are already provided by tailwindcss-language-server.
- Neovim v0.9 or higher (v0.10 is recommended)
- tailwindcss-language-server >=
v0.0.14
(can be installed using Mason or npm) html
,css
,tsx
and other language Treesitter grammars (using nvim-treesitter)- Neovim node-client (using npm)
Using lazy.nvim:
-- tailwind-tools.lua
return {
"luckasRanarison/tailwind-tools.nvim",
name = "tailwind-tools",
build = ":UpdateRemotePlugins",
dependencies = {
"nvim-treesitter/nvim-treesitter",
"nvim-telescope/telescope.nvim", -- optional
"neovim/nvim-lspconfig", -- optional
},
opts = {} -- your configuration
}
If you are using other package managers, you need register the remote plugin by running the :UpdateRemotePlugins
command, then call setup
to enable the lua plugin:
require("tailwind-tools").setup({
-- your configuration
})
Important
Neovim v0.10 is required for vscode-like inline color hints.
By default, the plugin automatically configures tailwindcss-language-server using nvim-lspconfig, if it is installed. Make sure you do not set up the server elsewhere.
Here is the default configuration:
---@type TailwindTools.Option
{
server = {
override = true, -- setup the server from the plugin if true
settings = {}, -- shortcut for `settings.tailwindCSS`
on_attach = function(client, bufnr) end, -- callback triggered when the server attaches to a buffer
},
document_color = {
enabled = true, -- can be toggled by commands
kind = "inline", -- "inline" | "foreground" | "background"
inline_symbol = " ", -- only used in inline mode
debounce = 200, -- in milliseconds, only applied in insert mode
},
conceal = {
enabled = false, -- can be toggled by commands
min_length = nil, -- only conceal classes exceeding the provided length
symbol = "", -- only a single character is allowed
highlight = { -- extmark highlight options, see :h 'highlight'
fg = "#38BDF8",
},
},
cmp = {
highlight = "foreground", -- color preview style, "foreground" | "background"
},
telescope = {
utilities = {
callback = function(name, class) end, -- callback used when selecting an utility class in telescope
},
},
-- see the extension section to learn more
extension = {
queries = {}, -- a list of filetypes having custom `class` queries
patterns = { -- a map of filetypes to Lua pattern lists
-- example:
-- rust = { "class=[\"']([^\"']+)[\"']" },
-- javascript = { "clsx%(([^)]+)%)" },
},
},
}
Available commands:
TailwindConcealEnable
: enables conceal for all buffers.TailwindConcealDisable
: disables conceal.TailwindConcealToggle
: toggles conceal.TailwindColorEnable
: enables color hints for all buffers.TailwindColorDisable
: disables color hints.TailwindColorToggle
: toggles color hints.TailwindSort(Sync)
: sorts all classes in the current buffer.TailwindSortSelection(Sync)
: sorts selected classes in visual mode.TailwindNextClass
: moves the cursor to the nearest next class node.TailwindPrevClass
: moves the cursor to the nearest previous class node.
Note
In normal mode, TailwindNextClass
and TailwindPrevClass
can be used with a count to jump through multiple classes at once.
Utility function for highlighting colors in nvim-cmp using lspkind.nvim:
-- nvim-cmp.lua
return {
"hrsh7th/nvim-cmp",
dependencies = {
"luckasRanarison/tailwind-tools.nvim",
"onsails/lspkind-nvim",
-- ...
},
opts = function()
return {
-- ...
formatting = {
format = require("lspkind").cmp_format({
before = require("tailwind-tools.cmp").lspkind_format
}),
},
}
end,
},
Tip
You can extend it by calling the function and get the returned vim_item
, see the nvim-cmp wiki to learn more.
The plugins registers by default a telescope extension that you can call using :Telescope tailwind <subcommand>
Available subcommands:
-
classes
: Lists all the classes in the current file and allows to jump to the selected location. -
utilities
: Lists all utility classes available in the current project with a custom callback.
The plugin already supports many languages, but requests for additional language support and PRs are welcome. You can also extend the language support in your configuration by using Treesitter queries or Lua patterns (or both).
Treesitter queries are recommended because they can precisely capture the class values at the AST level, but they can be harder to write. If you are not familiar with Treesitter queries, check out the documentation from Neovim or Treesitter.
You can define custom queries for a filetype by adding the filetype to the queries
list, like this:
{
extension = {
queries = { "myfiletype" },
}
}
The plugin will search for a class.scm
file (classexpr) associated with that filetype in your runtimepath
. You can use your Neovim configuration folder to store queries in the following way:
~/.config/nvim
.
├── init.lua
├── lua
│ └── ...
└── queries
└── myfiletype
└── class.scm
The class.scm
file should contain a query used to extract the class values for a given filetype. The class value should be captured using @tailwind
, as shown in the following example:
; queries/myfiletype/class.scm
(attribute
(attribute_name) @_attribute_name
(#eq? @_attribute_name "class")
(quoted_attribute_value
(attribute_value) @tailwind))
Note that quantified captures (using +
or ?
) cannot be captured using @tailwind
. Instead, you must capture the parent node using @tailwind.inner
.
(arguments
(_)+) @tailwind.inner
You can also define node offsets by using the #set!
directive and assign the start
or end
variables to some offset values (defaults to 0).
((postcss_statement
(at_keyword) @_keyword
(#eq? @_keyword "@apply")
(plain_value)+) @tailwind.inner
(#set! @tailwind.inner "start" 1))
Lua patterns are easier to write, but they have some limitations. Unlike Treesitter queries, Lua patterns cannot capture nested structures, they are limited to basic pattern matching.
You can define custom patterns by attaching a list of patterns to filetypes. Each pattern should have exactly one capture group representing the class value, as shown below:
{
extension = {
patterns = {
javascript = { "clsx%(([^)]+)%)" },
},
}
}
Tip
Lua patterns can be combined with Treesitter queries. You can use both for a single filetype to get the combined results.
Here are some related projects:
- tailwindcss-intellisense (official vscode extension)
- tailwind-sorter.nvim (uses external scripts)
- tailwind-fold (vscode extension)
- tailwind-fold.nvim
- document-color.nvim (archived)
Read the documentation carefully before submitting any issue.
Feature and pull requests are welcome.