AgentSkillsCN

tailwind-sort

为提升可读性,对 Tailwind 类进行排序。适用于当 JSX 元素或 cva 包含超过八种 Tailwind 类时,或当类被提取为可复用的常量/数组时使用。

SKILL.md
--- frontmatter
name: tailwind-sort
description:
  Sort Tailwind classes for legibility. Use when a JSX element or cva has more
  than eight Tailwind classes, or when classes are extracted into a reusable constant/array.

Sort Tailwind classes on a JSX element or a CVA object

When to group

Inline className: Only group/sort when there are 8+ Tailwind class names on a single element. For 1-7 classes, leave them as a plain string (className="...") — do not wrap in cn(...) just for formatting.

Extracted constants / reusable arrays: When classes are pulled into a named variable (e.g. const navItemBaseClasses = [...]) or a cva base, grouping with comments is appropriate at any length because the variable exists specifically to document a shared style contract.

cn is not a formatting tool. Only use cn(...) when you need conditional/dynamic class merging, or when an element genuinely has 8+ static classes that benefit from grouping. A short static className string is always preferred over a cn(...) call with no conditions.

Group labels

Group labels are allowed when they materially improve readability. As a rule of thumb:

  • 1 group: no labels.
  • 2-3 groups: labels are allowed if they clarify intent.
  • 4+ groups: labels are recommended.

Remember: only label groups if there are more than 3 groups.

Here's an example in CVA (components/ui/button: buttonVariants).

From:

ts
cva(
  "ring-offset-background focus-visible:ring-ring inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
  {}
)

To:

ts
cva(
  [
    // layout
    "inline-flex items-center justify-center gap-2",
    // shape
    "rounded-md",
    // typography
    "text-sm font-medium whitespace-nowrap",
    // focus
    "ring-offset-background focus-visible:ring-ring",
    // disabled
    "disabled:pointer-events-none disabled:opacity-50",
    // svg
    "[&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
    // transitions
    "transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2",
  ],
  {}
)

Here's an example in JSX (components/ui/tabs: TabsTrigger).

From:

tsx
/*  */
<div
  className={cn(
    "ring-offset-background focus-visible:ring-ring data-[state=active]:bg-background data-[state=active]:text-foreground inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm",
    className
  )}
/>

To:

tsx
<div
  className={cn(
    // layout
    "inline-flex items-center justify-center",
    // shape
    "rounded-sm px-3 py-1.5",
    // typography
    "text-sm font-medium whitespace-nowrap",
    // focus
    "ring-offset-background",
    "focus-visible:ring-ring focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none",
    // disabled
    "disabled:pointer-events-none disabled:opacity-50",
    // transitions
    "transition-all",
    // states
    "data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
    className
  )}
/>

Please note that:

  • cn is a util that merges duplicate Tailwind classes, and handles aggregating a string of classes from all conditionals. Uses clsx and tailwind-merge.
  • cx is calls-variance-authority's exported version of clsx. Use this instead of cn because cn will remove text-* classes but we need those unduplicated because they can both style colour and font-size! import { VariantProps, cva, cx } from "class-variance-authority";
  • cva from class-variance-authority allows us to create type-safe UI components with variants. See: https://cva.style/docs