Claude Fable 5.1 & GPT-6 Astra packages are live

Tailwind

Free

Tailwind CSS — design tokens over arbitrary values, component extraction, conditional classes without string concatenation, and dark mode.

210 lines8.1 KB Mistral Frontend
targetModels
Mistral Medium 3.5Mistral Large 3Mistral Small 4Mistral FamilyFuture Mistral Models
name
tailwind
category
Frontend
description
Tailwind CSS — design tokens over arbitrary values, component extraction, conditional classes without string concatenation, and dark mode.
license
MIT
author
Agent.md maintainers
last-verified
reviewed-by
unreviewed
<!-- Generated from models/_canonical by scripts/build-model-variants.js. Edit the canonical source, not this file. Behavioural profile for Mistral: scripts/model-profiles.json -->

#How to apply this file

Each section opens with one imperative line; apply every rule in the section it introduces. Do not summarise or skip a section.


#Purpose

Rules for using Tailwind. Its value is a constrained design system applied directly where markup lives — no naming, no dead CSS, no cascade surprises.

The failure mode is the opposite: arbitrary values everywhere, duplicated class strings, and unreadable markup. Every rule here protects the constraint.


#Configure tokens; stop reaching for arbitrary values

[INST] Apply every rule in this section: Configure tokens; stop reaching for arbitrary values. [/INST]

css
/* app.css — Tailwind v4 configures in CSS */
@import "tailwindcss";

@theme {
  --color-brand-500: oklch(0.62 0.19 259);
  --color-surface:   oklch(0.98 0 0);
  --spacing-18:      4.5rem;
  --radius-card:     0.75rem;
  --font-display:    "Inter Variable", sans-serif;
}

Then bg-brand-500, p-18, rounded-card work everywhere, and the value is defined once.

p-[13px] and text-[#3b82f6] bypass the system. Each one is a value nobody else knows about and nothing can update. Use them for genuine one-offs (a translate-y-[3px] optical adjustment), and add a token the second time a value appears.

Prefer the scale for colours, spacing, radii, shadows and typography.

Never hard-code a brand colour as a hex value in a utility class. When the brand changes, they are unfindable.


#Extract components, not @apply soup

[INST] Apply every rule in this section: Extract components, not @apply soup. [/INST]

tsx
// The right abstraction is a component: typed, testable, composable
export function Button({ variant = "primary", className, ...props }) {
  return <button className={cn(base, variants[variant], className)} {...props} />;
}
css
/* The wrong one: reinvents CSS classes and loses everything Tailwind gave you */
.btn { @apply px-4 py-2 rounded font-medium bg-brand-500 text-white; }

@apply reintroduces the naming problem, the indirection, and the dead-CSS problem Tailwind exists to remove. Reserve it for a handful of genuinely global primitives (a focus ring, a prose block), not for components.

For variants, use cva (class-variance-authority) or tailwind-variants — they give typed variants and handle conflict resolution:

ts
const button = cva("inline-flex items-center rounded-card font-medium", {
  variants: {
    variant: { primary: "bg-brand-500 text-white", ghost: "bg-transparent hover:bg-surface" },
    size:    { sm: "h-8 px-3 text-sm", md: "h-10 px-4" },
  },
  defaultVariants: { variant: "primary", size: "md" },
});

Always accept a className prop and merge it with twMerge so a caller can override — otherwise bg-red-500 and bg-brand-500 both land in the class list and the winner depends on stylesheet order, not on intent.


#Conditional classes

[INST] Apply every rule in this section: Conditional classes. [/INST]

tsx
// Broken: the scanner never sees the full class name, so the CSS is not generated
<div className={`text-${color}-500 p-${size}`} />

// Correct: complete class names in the source
const COLOR = { error: "text-red-500", ok: "text-green-600" } as const;
<div className={COLOR[status]} />

Tailwind generates CSS by scanning source files for literal class strings. A dynamically constructed name produces no CSS and no error — the style is simply absent, and usually only in the production build.

Use clsx/cn for conditionals, and twMerge to resolve conflicts:

tsx
<div className={cn("p-4 text-sm", isActive && "bg-brand-50", className)} />

#Responsive, state and dark mode

[INST] Apply every rule in this section: Responsive, state and dark mode. [/INST]

Tailwind is mobile-first: an unprefixed utility applies at all sizes, and md: applies from that breakpoint up.

tsx
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4" />
  • Use state variants rather than JavaScript: hover:, focus-visible:, disabled:, aria-expanded:, data-[state=open]:, group-hover:, peer-checked:.
  • focus-visible: rather than focus: for focus rings, and never remove the ring without replacing it — keyboard users need it. → Testing/accessibility
  • Dark mode: define both palettes as tokens and let dark: switch them. Driving it from a data-theme attribute set before hydration avoids a flash and a hydration mismatch. → Frontend/hydration
  • Honour motion-reduce: for anything animated.

#Keep markup readable

[INST] Apply every rule in this section: Keep markup readable. [/INST]

A 30-class element is a real cost. Reduce it by:

  • Extracting a component as soon as the same string appears twice.
  • Grouping classes in a consistent order — enforce with prettier-plugin-tailwindcss so ordering never appears in a diff.
  • Using logical properties (ps-4, me-2) where the application supports RTL.
  • Letting the parent own layout (flex, gap) and children own themselves; a child setting its own margin for a specific parent is not reusable.

Tailwind's output is already minimal — it emits only the classes it found. The remaining size concern is your own markup, not the stylesheet.


#Anti-patterns

[INST] Apply every rule in this section: Anti-patterns. [/INST]

Anti-patternWhy it failsFix
Arbitrary values everywhereBypasses the design systemDefine tokens in @theme
Hex colours in utility classesUnfindable when the brand changesNamed colour tokens
@apply for componentsReintroduces naming and dead CSSReact components
Dynamic class-name constructionClass never generated; silent missing styleFull literal strings in a map
No twMerge on merged classesConflicts resolved by stylesheet ordercn with twMerge
Component not accepting classNameCallers cannot adjust anythingAccept and merge it
Duplicated long class stringsDrift between copiesExtract a component
Desktop-first breakpointsFights Tailwind's mobile-first modelBase styles, then md: up
focus: instead of focus-visible:Rings on mouse clickfocus-visible:
Removing the focus ringKeyboard navigation becomes invisibleReplace, never remove
JavaScript for hover/open statesMore code than a variantState and data-* variants
Dark mode via duplicated markupTwo trees to maintainToken pairs plus dark:
Unordered class stringsNoisy diffsPrettier plugin
Ignoring motion-reduce:Vestibular discomfort; accessibility failureRespect the preference

#Checklist

  • Verify: Design tokens are defined centrally and used instead of arbitrary values
  • Verify: Arbitrary values are rare, one-off and justified
  • Verify: No raw hex colours appear in utility classes
  • Verify: Repeated class strings are extracted into components
  • Verify: @apply is limited to a few global primitives
  • Verify: Variants are defined with cva or tailwind-variants
  • Verify: Components accept and merge a className prop with twMerge
  • Verify: No class name is built by string interpolation
  • Verify: Conditional classes come from complete literal strings
  • Verify: Layout is mobile-first with breakpoints applied upward
  • Verify: Interactive states use Tailwind variants rather than JavaScript
  • Verify: focus-visible: rings are present on every interactive element
  • Verify: Dark mode uses token pairs and is set before hydration
  • Verify: motion-reduce: is honoured for animations
  • Verify: Class ordering is enforced by the Prettier plugin