Skip to content

JoshuaErney/css-lens

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

27 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

CSS Lens

A native Rust LSP server that powers the CSS Lens Zed extension.

It scans every .css file in your workspace and wires up a full set of HTML authoring features — completions, hover documentation, diagnostics, navigation, and refactoring — all backed by a real understanding of your stylesheet.


Features

Completions

  • Class completions — typing inside class="..." suggests every class name found in your CSS files, filtered by what you've already typed (case-insensitive)
  • Already-used filtering — classes already present in the same attribute are excluded from suggestions
  • Smart spacing — inserted names automatically get a leading or trailing space when the cursor is flush against another class name
  • ID completions — typing inside id="..." suggests ID selectors; suggestions stop once a value is present (IDs are single-value)
  • CSS variable completions — typing -- inside style="..." suggests custom properties (--primary-color, etc.) found in your CSS
  • Property and value completions — typing inside style="..." suggests CSS property names and keyword values (e.g. display: flex)
  • Animation name completions — when the cursor is in an animation-name: value inside style="", suggests every @keyframes name defined in your CSS

Hover Tooltips

Hovering a class name in class="...", an ID in id="...", or a variable name in style="..." shows:

  • The full selector (e.g. .btn:is(.active, .focused))
  • The source file and line number (e.g. styles.css:42)
  • The @media or @supports context if the rule is inside one
  • The @layer context if the rule is nested inside a named cascade layer (e.g. @layer base)
  • The CSS specificity score as (a,b,c) — IDs, classes, and element types all counted correctly
  • Color values — hex, rgb(), and hsl() values in the rule's properties are shown inline
  • The full property block in a syntax-highlighted code fence
  • All definitions listed when the same name is defined in multiple files
  • Declared value of a CSS custom property when hovering a --variable-name in style=""
  • CSS custom property hover in .css files — hovering --variable-name directly inside a CSS file shows its declared value, not just in HTML attributes

Diagnostics

  • Undefined class/ID — class and ID names in HTML are checked against only the CSS files linked from that specific page via <link rel="stylesheet"> (and their @import chains); a class that exists in an unlinked file is still flagged as an error on pages that never load it
  • Duplicate selector — if the same class or ID is defined more than once in the same CSS file, every definition after the first is flagged as a warning
  • Unused-selector hint — CSS selectors not referenced in any currently-open HTML file receive a soft hint; suppressed when no HTML files are open; also suppressed for classes referenced in plain JavaScript via classList.add/remove/toggle, getElementsByClassName, or querySelector/querySelectorAll so vanilla DOM manipulation never triggers false positives
  • Duplicate class in attributeclass="btn btn" with the same token listed more than once is flagged as a warning at the duplicate occurrence

Navigation

  • Go to definitionCmd+Click a class in class="..." or an ID in id="..." to jump to its definition in the source CSS file; when multiple files define the same name the editor presents a picker; also works for @keyframes names in animation-name:
  • Find all references — from a class or ID in an HTML attribute, finds every HTML file in the workspace that uses the same name
  • Workspace symbol search — the Zed symbol palette lists every CSS class and ID selector across the entire workspace, searchable by name, with the source filename shown as context

Refactoring

  • Rename — rename a class or ID from either an HTML attribute or a CSS selector definition; the LSP updates the CSS selector and every HTML attribute reference across the workspace atomically
  • Create class/ID — when an undefined class or ID is flagged in HTML, a Quick Fix code action appends the new rule to the nearest linked CSS file
  • Remove unused rule — an unused-selector hint offers a Quick Fix to delete the entire rule block from the CSS file; a guard ensures the action is only offered when every co-selector in the block is also unused

CSS Parsing

  • Follows @import chains recursively (with cycle detection)
  • Handles @media, @supports, @layer, and other block at-rules via a proper brace-depth-aware parser; tracks @layer names so selectors nested inside named layers are attributed correctly
  • Extracts class and ID selectors from inside functional pseudo-classes — :is(.foo, .bar), :has(.child), :where(.group), :not(.active) — so those names are fully indexed for completions, hover, and diagnostics
  • Extracts both class selectors (.btn) and ID selectors (#hero)
  • Parses CSS custom properties (--name: value) for variable completions and hover
  • Parses @keyframes names for animation-name completions, hover, and go-to-definition
  • Strips block comments while preserving line numbers
  • Skips files larger than 500 KB to avoid stalling on minified bundles
  • Excludes node_modules and hidden directories

Snippets

51 HTML snippets are bundled with the extension and load automatically — no manual setup needed.

What's included:

  • html5 — full page boilerplate with meta tags, Open Graph, theme-color, icons, skip-navigation link, and a semantic <header> / <main> / <footer> layout
  • link<link rel="stylesheet"> for quickly adding a stylesheet reference
  • meta — meta tag with name and content attributes
  • template<template> element for vanilla JS cloneNode patterns
  • slot — named <slot> for web components
  • Element snippets for every common HTML element, each pre-wired with the attributes that matter — type and aria-label on buttons, alt and loading on images, autocomplete on inputs, scope on table headers, datetime on <time>, cite on blockquotes, low/high/optimum on meters, defer/async on scripts, and more

How it works

The LSP server is a plain Rust binary that speaks the Language Server Protocol over stdio. It is downloaded automatically by the Zed extension at install time — you do not need to install it manually.

On startup it walks every .css file in the workspace, parses all selectors and custom properties into an in-memory map, and responds to LSP requests from the editor. File changes are picked up incrementally via workspace/didChangeWatchedFiles — only the changed file is re-parsed. Diagnostics for open HTML files are pushed automatically whenever the CSS map changes.


Building from source

Requires Rust installed via rustup.

git clone https://github.com/joshuaerney/css-lens
cd css-lens/lsp
cargo build --release
# binary is at target/release/css-lens

Cross-compilation targets

# macOS Apple Silicon (native)
cargo build --release --target aarch64-apple-darwin

# macOS Intel
cargo build --release --target x86_64-apple-darwin

# Linux x86_64 (requires cargo-zigbuild or a GNU cross-toolchain)
cargo zigbuild --release --target x86_64-unknown-linux-gnu

New releases are built and published automatically by the GitHub Actions workflow in .github/workflows/release.yml when a v* tag is pushed.


Release asset naming

GitHub release assets must be named exactly as follows for the Zed extension to find them:

Platform Asset filename
macOS Apple Silicon css-lens-{version}-aarch64-apple-darwin.tar.gz
macOS Intel css-lens-{version}-x86_64-apple-darwin.tar.gz
Linux x86_64 css-lens-{version}-x86_64-unknown-linux-gnu.tar.gz

Each .tar.gz must contain a single executable named css-lens.


License

MIT — see LICENSE.

About

Native Rust LSP server powering the CSS Lens Zed extension. Full CSS intelligence for HTML — autocomplete, diagnostics, hover, go-to-definition, find references, and rename for class and ID attributes.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages