Skip to content

@custom-variant

Define a variant with a selector, or with a block that marks the styled content with @slot.

@custom-variant name (selector);
@custom-variant name {
  selector {
    @slot;
  }
}
@custom-variant name scope(document) ...;

@custom-variant defines a variant. Write it at the top level of the stylesheet.

Example

The short form takes a selector in parentheses. The block form wraps the content with any rules, and @slot marks where the content goes.

Stylesheet

@custom-variant theme-dark (&:where([data-theme="dark"], [data-theme="dark"] *));

@custom-variant hocus {
  &:hover,
  &:focus-visible {
    @slot;
  }
}

Source

import { style } from "stillwind/react";

export const link = style("theme-dark:text-white hocus:underline");

Compiled code

export const link = {className:"sw_r7v8w"};

Generated stylesheet without preflight

/*! stillwind v0.0.0 | MIT License | https://stillwind.dev */
/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */
@layer properties, theme, base, components, utilities;

@layer theme {
  :root, :host {
    --color-white: #fff;
  }
}

@layer utilities {
  .sw_r7v8w:where([data-theme="dark"], [data-theme="dark"] *) {
    color: var(--color-white);
  }

  .sw_r7v8w:hover, .sw_r7v8w:focus-visible {
    text-decoration-line: underline;
  }
}

The block form can also contain at-rules, such as @media or @supports, around @slot.

Selectors without &

& refers to the element that the utility styles. As in CSS Nesting, a selector without & selects descendants: @custom-variant expanded ([aria-expanded="true"]) means & [aria-expanded="true"]. implicit-nesting-selector reports such a selector, and its fix writes the & without changing the meaning.

Document-level elements

scope(document) after the name keeps selectors without & as written, so the variant can select a document-level element such as :host or :root.

Stylesheet

@custom-variant shell scope(document) (:host);

Source

import { style } from "stillwind/react";

export const css = style.css("shell:block shell:p-4 [button]:rounded-md");

Compiled code

export const css = "/*! stillwind v0.0.0 | MIT License | https://stillwind.dev */\n/*! tailwindcss v4.3.3 | MIT License | https://tailwindcss.com */\n:where(:root, :host){--spacing:0.25rem;--radius-md:0.375rem;}:host{display:block;}:host{padding:calc(var(--spacing) * 4);}button{border-radius:var(--radius-md);}";

In style.css(), the first such variant selects the element that the utility styles. In style(), the rule stays document-wide, and document-scoped-selector reports it.

Definitions with the same name

A later variant definition with the same name replaces the earlier one. duplicate-definition reports it. To match several selectors, list them in one definition.

The order of variant definitions decides the output order of rules with different variants. A definition in your stylesheet comes after the built-in variants.