Utility classes are the default. Custom CSS is the escape hatch.
Skill(frontend:css)
Skip only for trivial class additions where no CSS reasoning is needed.
Tailwind CSS uses CSS-first configuration: design tokens live in @theme, custom utilities use @utility, and there is
no JavaScript configuration file. Constrain yourself to the design system; break out only with intention.
References
- Theme — [
${CLAUDE_SKILL_DIR}/references/theme-configuration.md]: Theme tokens,@themeoptions, namespace mapping, color system - Class authoring — [
${CLAUDE_SKILL_DIR}/references/class-authoring.md]: Class composition, variants, dark mode, breakpoints - Custom utilities — [
${CLAUDE_SKILL_DIR}/references/custom-utilities-and-variants.md]:@utility,@custom-variant, directives,@source - Layout — [
${CLAUDE_SKILL_DIR}/references/layout.md]: Display, position, flexbox, grid, alignment, order utilities - Sizing — [
${CLAUDE_SKILL_DIR}/references/sizing-and-spacing.md]: Spacing scale, width/height, padding/margin, borders, box model - Typography — [
${CLAUDE_SKILL_DIR}/references/typography.md]: Font properties, text spacing, styling, decoration, layout - Backgrounds — [
${CLAUDE_SKILL_DIR}/references/backgrounds-and-effects.md]: Gradients, shadows, rings, opacity, SVG, filters - Transforms — [
${CLAUDE_SKILL_DIR}/references/transforms-and-animations.md]: Transitions, animations, 2D/3D transforms, masks - Framework — [
${CLAUDE_SKILL_DIR}/references/framework-integration.md]: Preflight, CSS Modules, class binding (React, Vue, Svelte)
Entry Point and Installation
- Single import:
@import "tailwindcss";— provides preflight reset, theme variables, and all utilities. No@tailwind base/components/utilities(v3 syntax) - Vite: install
@tailwindcss/viteplugin. PostCSS: install@tailwindcss/postcss. CLI:npx @tailwindcss/cli -i input.css -o output.css - No
tailwind.config.jsin v4 — all configuration lives in CSS via@theme - Remove
postcss-importandautoprefixer— v4 handles both internally - Do not use Sass, Less, or Stylus with Tailwind v4 — Tailwind is the preprocessor (handles
@import, nesting, variables, vendor prefixes)
Theme Configuration (@theme)
Core Rules
@themedefines design tokens that generate utility classes — not equivalent to:root. Use@themefor values needing utilities; use:rootfor CSS variables that only needvar()access@thememust be top-level (not nested under selectors or media queries)- All
@themevalues compile to:root { }CSS vars in output - Only used CSS vars are emitted by default
- Semantic token names:
--color-primary,--color-surface— not--color-blue-500or--color-gray-100 - OKLCH for custom colors:
oklch(0.72 0.11 178)— perceptually uniform, works with CSScolor-mix()
@theme Options
@theme { }— Default: only emit used vars@theme static { }— Always emit all vars@theme inline { }— Inlinevar()references into utility output
Use @theme inline when a token references another variable — prevents CSS variable resolution failures in the cascade.
Namespace → Utility Mapping
--color-*→bg-*,text-*,border-*,ring-*,fill-*,stroke-*, etc.--font-*→font-*(family)--text-*→text-*(size)--font-weight-*→font-*(weight)--tracking-*→tracking-*--leading-*→leading-*--breakpoint-*→ Responsive variants:sm:*,md:*--container-*→ Container query variants:@sm:*, andmax-w-*--spacing-*or--spacing→px-*,py-*,m-*,w-*,h-*, etc.--radius-*→rounded-*--shadow-*/--inset-shadow-*→shadow-*/inset-shadow-*--blur-*→blur-*--ease-*→ease-*--animate-*→animate-*
Breakpoints generate variants, not utilities. Colors generate multiple utility families from a single namespace.
Extending, Replacing, Resetting
- Extend: Add new tokens alongside defaults — just declare new vars in
@theme - Override: Redeclare a default var to change its value
- Reset namespace:
--color-*: initialremoves all defaults in that namespace - Reset everything:
--*: initialfor fully custom theme - Disable specific colors:
--color-lime-*: initial
Colors
- 22 color families x 11 steps (50-950) plus
blackandwhite - Every
--color-*token generates utilities acrossbg-*,text-*,border-*,ring-*,fill-*,stroke-*, etc. - Opacity modifier:
bg-sky-500/50— per-property, not whole-element --alpha()for CSS opacity: compiles tocolor-mix(in oklab, ...)- Never use
bg-opacity-*(removed in v4) — alwaysbg-color/opacity
Sharing Themes
Put @theme in a standalone CSS file and @import it after @import "tailwindcss".
Class Authoring
Fundamental Rules
- Complete class names only. Never concatenate or interpolate —
text-red-600yes,`text-${color}-600`never. Tailwind scans source files as plain text - Map dynamic values to static class string lookups
- Prettier plugin for ordering. Install
prettier-plugin-tailwindcss— do not manually sort classes - CSS variable shorthand:
bg-(--brand-color)— parenthesis syntax auto-wraps invar(). Do not usebg-[var(--brand)](v3 verbose form) - Modifiers stack left-to-right (v4):
dark:lg:hover:bg-indigo-600. v3 was right-to-left — reverse stacking order when migrating - Arbitrary values for one-offs only. Repeated values belong in
@theme - Important suffix:
bg-red-500!— the!goes at end, after all modifiers - Conflict resolution: Last class in the generated stylesheet wins, not last in the HTML attribute. Don't rely on attribute order — use conditional rendering
- Underscores = spaces in arbitrary values:
grid-cols-[1fr_500px_2fr]. Escape for literal underscore:content-['hello\_world'] - Type hints for ambiguous CSS vars:
text-(length:--my-var)for font-size,text-(color:--my-var)for text color
Responsive Breakpoints (Mobile-First)
Unprefixed = all sizes. Prefix = that breakpoint and up.
-
sm:— 40rem (640px) -
md:— 48rem (768px) -
lg:— 64rem (1024px) -
xl:— 80rem (1280px) -
2xl:— 96rem (1536px) -
Don't use
sm:to mean "mobile only" — it means 640px and up -
Unprefixed for mobile base, override at breakpoints
-
Range targeting:
md:max-xl:flex(only between md and xl) -
Arbitrary breakpoints:
min-[900px]:grid-cols-3 -
Custom breakpoints: define in
@theme { --breakpoint-xs: 30rem; }
Container Queries
@containeron parent,@md:flex-rowon children- Named containers:
@container/main+@sm/main:flex-col - Sizes range
@3xs(16rem) through@7xl(80rem) - Arbitrary:
@min-[475px]:flex-row - Customize via
--container-*in@theme
State Variants
- Pseudo-classes:
hover:,focus:,active:,visited:,focus-visible:,focus-within:,disabled:,required:,invalid:,checked:,read-only:,indeterminate:,first:,last:,odd:,even:,empty: - Conditional:
has-checked:(element has checked descendant),not-focus:(element is NOT focused) - Group (style children based on parent):
groupon parent,group-hover:text-whiteon child. Named groups:group/item+group-hover/item:visiblefor nested disambiguation - In-*: Like group but without marking the parent:
in-focus:opacity-100 - Peer (style based on preceding sibling):
peeron sibling,peer-invalid:visibleon target. Named peers for disambiguation - has-* variant:
has-checked:bg-indigo-50,group-has-[a]:block,peer-has-checked:ring-2
Dark Mode
- Default is
prefers-color-schememedia query —dark:works without config - Manual toggle via
@custom-variant dark (&:where(.dark, .dark *)); - Data attribute:
@custom-variant dark (&:where([data-theme=dark], [data-theme=dark] *)); - Prevent FOUC: Theme-detection script must be inline in
<head>, never in a deferred bundle color-schemefor native UI:scheme-light dark:scheme-darkon<html>matches scrollbars and form controls to active theme
Custom Utilities and Variants
@utility
- Custom utilities are inserted into the
utilitieslayer automatically and support all variants (hover:,focus:,lg:, etc.) - Simple:
@utility content-auto { content-visibility: auto; } - Complex with nesting:
@utility scrollbar-hidden { &::-webkit-scrollbar { display: none; } } - Functional (accepts argument): use wildcard
@utility tab-*with--value() --value()resolution modes:--value(--ns-*)(theme key),--value(integer)(bare value),--value([integer])(arbitrary value),--value("inherit")(literal)- Multiple modes:
--value(--tab-size-*, integer, [integer]) --modifier()reads the modifier portion (text-lg/tight)- Negative values: register separate
-utility-*form - Prefer
@utilityand@custom-variantover JS plugins for new code
@custom-variant
- Shorthand:
@custom-variant theme-midnight (&:where([data-theme="midnight"] *)); - Block form with
@slotfor multiple rules or media queries - Override built-in
darkvariant for class-based toggling
Other Directives
@variant: Apply variants in custom CSS:@variant dark { background: black; }@apply: Compose utilities into custom CSS — last resort only. Place in@layer components. Single-element patterns only@reference: Import theme context in Vue/Svelte<style>blocks or CSS Modules without duplicating output CSS@plugin: Load JS plugins. CSS-native@utility/@custom-variantpreferred@layerprecedence:base<components<utilities. Utilities always win@source: Register additional scan paths, exclude paths, safelist with@source inline()using brace expansion
Build-Time Functions
--alpha(var(--color-lime-300) / 50%)→color-mix(in oklab, ...)--spacing(4)→calc(var(--spacing) * 4)— also valid in arbitrary valuestheme()is deprecated — usevar(--color-red-500)instead
Content Detection (@source)
- Auto-scans all project files except
.gitignored,node_modules, binaries, CSS files, lock files @source "../node_modules/@my-company/ui-lib"for external packages@source not "../src/legacy"to exclude directories@source inline("underline")for safelisting (brace expansion supported)@source not inline(...)to explicitly exclude from generation@import "tailwindcss" source(none)disables auto-detection entirely@import "tailwindcss" source("../src")sets base scan path
Component Extraction
- Template components over
@apply. In React/Vue/Svelte, extract a component. In server templates, extract a partial.@applyis the last resort @applyonly for single-element patterns — multi-element structures belong in template components- Place
@apply-based classes in@layer componentsso utilities can override - Acceptable
@applyuses: third-party library overrides, legacy HTML you don't control
Layout
Use flex for 1D flow, grid for 2D placement. gap over margin hacks.
sr-onlyfor visually hidden, screen-reader accessible;not-sr-onlyto reverse.hiddenremoves from flow;invisiblekeeps spaceabsolute inset-0(fill parent),sticky top-0 z-10(sticky header)flex-1(grow/shrink, ignore initial),flex-auto(respect initial),flex-none(fixed size)- Grid:
grid-cols-<n>,col-span-<n>,col-span-full,grid-flow-dense - Gap:
gap-<n>,gap-x-<n>,gap-y-<n>— works in both flex and grid isolatecreates a new stacking context withoutz-index
See ${CLAUDE_SKILL_DIR}/references/layout.md for full display, position, flexbox, grid, alignment, order, and
visibility utility catalogs.
Sizing and Spacing
--spacing drives all spacing utilities. 1 unit = 0.25rem (4px). Customize: @theme { --spacing: 4px; }.
Key Patterns
- Width/height:
w-<n>,h-<n>(spacing scale),w-<fraction>(percentage),w-full,w-screen,w-dvw,h-dvh.size-<n>sets both - Min/max:
min-w-*,max-w-*,min-h-*,max-h-* - Padding:
p-*(all),px-*/py-*,ps-*/pe-*(logical) - Margin: same prefixes plus
autoand negatives (-mt-4).mx-autocenters block elements - Prefer
gap-*with flex/grid overspace-x-<n>/space-y-<n>
Borders
- Width:
border,border-<n>, per-side (border-t,border-s/border-e) - v4 default is
currentColor(v3 wasgray-200) — always specify color - Divide:
divide-x-<n>,divide-y-<n>,divide-{color}between children
Border Radius
v4 scale shift: rounded without suffix maps to xs size (was md in v3). Per-side, per-corner, and logical
variants (rounded-s-*, rounded-ss-*) available. See ${CLAUDE_SKILL_DIR}/references/sizing-and-spacing.md for the
full scale table.
Outlines and Box Model
outline-hiddenoveroutline-none— preserves outlines in forced-colors mode- Focus pattern:
focus:outline-2 focus:outline-offset-2 focus:outline-sky-500 box-border(default),box-content;overflow-auto,overflow-clipoverscroll-containprevents scroll chaining
See ${CLAUDE_SKILL_DIR}/references/sizing-and-spacing.md for the full spacing scale, width/height keywords, container
scale, viewport units, and box model details.
Typography
Key Rules
- Family:
font-sans,font-serif,font-mono. Custom via--font-*in@theme - Size:
text-xsthroughtext-9xl— each sets bothfont-sizeand defaultline-height. Override inline:text-sm/6,text-lg/loose - Weight:
font-thin(100) throughfont-black(900) tabular-numsfor tables/pricing — composable, reset withnormal-nums- Prefer
text-start/text-endovertext-left/text-rightfor i18n text-balancefor headings,text-prettyto prevent orphans in body texttruncatefor single-line overflow;line-clamp-<n>for multi-line- Text shadow (v4 new):
text-shadow-smthroughtext-shadow-lg
See ${CLAUDE_SKILL_DIR}/references/typography.md for full font properties, text spacing, styling, decoration, and text
layout utility catalogs.
Backgrounds and Effects
Key v4 Changes
- Gradient syntax:
bg-linear-to-r(notbg-gradient-to-r),bg-radial,bg-conic. Default interpolation is oklab - Shadow scale shifted by one step from v3.
shadow-smin v3 =shadow-xsin v4 - Ring default: 1px currentColor (v3 was 3px blue) — use
ring-3for thick rings - Opacity modifier:
bg-{color}/{opacity}— neverbg-opacity-*
SVG and Media
fill-currentinherits parent text color — idiomatic for icon componentsobject-cover+ explicit dimensions for imagesaspect-square(1/1),aspect-video(16/9),aspect-3/2
See ${CLAUDE_SKILL_DIR}/references/backgrounds-and-effects.md for full gradient, shadow, ring, filter, backdrop, and
mask utility catalogs.
Transforms and Animations
- Use specific transitions:
transition-colors,transition-transform,transition-opacity— nevertransition-all - Compose transforms freely:
rotate-45 scale-110 translate-x-4 - Custom animations: define
--animate-*and@keyframesin@theme - 3D transforms: parent needs
transform-3dfortranslate-z-* - Backdrop blur for frosted glass:
backdrop-blur-sm bg-white/30
See ${CLAUDE_SKILL_DIR}/references/transforms-and-animations.md for full transition, animation, 2D/3D transform,
filter, and mask utility catalogs.
Motion and Accessibility
- Respect reduced motion. Gate animations with
motion-safe:or disable withmotion-reduce:transition-none sr-only/not-sr-onlyfor screen reader accessibilityforced-color-adjust-noneonly for elements where forced colors destroys essential visual information — always includesr-onlytext labelforced-colors:variant for styles only in forced colors mode- Add
role="list"on unstyled lists — VoiceOver doesn't announcelist-style: noneelements as lists
Framework Integration
Preflight
- Extends reset: headings unstyled, lists have no bullets, images are
display: block - v4 changes: buttons default
cursor: default, placeholder is text color at 50% opacity - Disable by importing
tailwindcss/theme.cssandtailwindcss/utilities.cssindividually
CSS Modules / SFC <style>
Each module is processed separately — causes slower builds and missing @theme context. Use @reference "../app.css"
in <style> blocks, or prefer CSS variables directly: background-color: var(--color-blue-500).
Class Binding
- React:
clsxfor conditional composition,cvafor variant APIs,cn=twMerge(clsx(...))for className overrides - Vue:
:class="{ 'bg-indigo-600': primary }"or array withcn() - Svelte 5:
class={cn("rounded-md", primary && "bg-indigo-600", className)}
Application
When writing Tailwind CSS:
- Apply all conventions silently — don't narrate rules being followed.
- Use utilities directly in markup. Reach for custom CSS only when utilities are insufficient.
- If an existing codebase contradicts a convention, follow the codebase and flag the divergence once.
When reviewing Tailwind CSS:
- Cite the specific violation and show the fix inline.
- Don't lecture — state what's wrong and how to fix it.
Integration
The CSS skill is a prerequisite — it provides specificity, box model, and layout knowledge that Tailwind abstracts but does not replace. Framework skills handle class binding in each framework.
Utility classes are the default. When in doubt, keep configuration in @theme and styling in markup.