/tailwind-tools.nvim

An unofficial Tailwind CSS integration and tooling for Neovim

Primary LanguageLuaMIT LicenseMIT

Important

This plugin is a community project and is NOT officially supported by Tailwind Labs.

tailwind-tools.nvim

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.

preview

Contents

Features

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:

Note

Language services like autocompletion, diagnostics and hover are already provided by tailwindcss-language-server.

Prerequisites

Installation

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
})

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
      -- exmaple:
      -- rust = { "class=[\"']([^\"']+)[\"']" },
      -- javascript = { "clsx%(([^)]+)%)" },
    },
  },
}

Commands

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.

Utilities

nvim-cmp

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.

telescope.nvim

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.

Extension

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

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 follwing 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

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.

Related projects

Here are some related projects:

Contributing

Read the documentation carefully before submitting any issue.

Feature and pull requests are welcome.