Writing custom CSS with @import tailwindcss
In Tailwind v4, a CSS file can mix the framework import with plain custom CSS: @import "tailwindcss"; followed by ordinary CSS rules like .my-custom-style { ... }. This replaces the v3 @tailwind base/components/utilities directives with a single @import statement.
Adding base styles via html/body classes or @layer base
To set page defaults (text color, background, font family) in Tailwind v4, either add utility classes directly to the html or body element (e.g. class="bg-gray-100 font-serif text-gray-900" on the html tag), or use the @layer base directive to add default styles for specific HTML elements:
@layer base {
h1 { font-size: var(--text-2xl); }
h2 { font-size: var(--text-xl); }
}
Note var(--text-2xl) references a theme variable rather than a config lookup.
Adding component classes via @layer components
Use @layer components in Tailwind v4 for reusable classes (card, btn, badge) that should still be overridable by utility classes. Example:
@layer components {
.card {
background-color: var(--color-white);
border-radius: var(--radius-lg);
padding: --spacing(6);
box-shadow: var(--shadow-xl);
}
}
Note the --spacing(6) function call is used to compute spacing values. Because these are in the components layer, a class like class="card rounded-none" will render as a card with square corners since rounded-none (a utility) overrides the component's border-radius.
@variant directive for applying variants in custom CSS
Tailwind v4 introduces the @variant directive to apply a Tailwind variant within custom CSS rules. Example:
.my-element {
background: white;
@variant dark {
background: black;
}
}
compiles to:
.my-element {
background: white;
@media (prefers-color-scheme: dark) {
background: black;
}
}
Multiple variants can be stacked like in HTML: @variant hover:focus { ... } compiles to nested &:hover { @media (hover: hover) { &:focus { ... } } }. To apply the same styles for multiple variants, separate them with commas: @variant hover, focus { ... } compiles to separate &:hover (wrapped in @media hover:hover) and &:focus blocks with the same declarations.
CSS color variables namespace
Colors are exposed as CSS variables in the --color-* namespace, so you can reference them in CSS with variables like --color-blue-500 and --color-pink-700.
Alpha function for color opacity in CSS
To quickly adjust the opacity of a color when referencing it as a variable in CSS, Tailwind includes a special --alpha() function. Usage example: background-color: --alpha(var(--color-gray-950) / 10%);
Adding custom colors with @theme
Use @theme to add custom colors to your project under the --color-* theme namespace. Example: @theme { --color-midnight: #121063; --color-tahiti: #3ab7bf; --color-bermuda: #78dcca; } makes utilities like bg-midnight, text-tahiti, and fill-bermuda available.
Overriding default colors with @theme
Override any of the default colors by defining new theme variables with the same name in @theme. For example, redefine --color-gray-50 through --color-gray-950 to use different OKLCH values.
Disabling default colors
Disable any default color by setting the theme namespace for that color to initial. Example: @theme { --color-lime-*: initial; --color-fuchsia-*: initial; }. This is useful for removing corresponding CSS variables from output for colors you don't intend to use.
Complete custom color palette
Use --color-*: initial to completely disable all default colors and define a custom palette. Example: @theme { --color-*: initial; --color-white: #fff; --color-purple: #3f3cbb; --color-midnight: #121063; }
Referencing other variables in @theme
Use @theme inline when defining colors that reference other CSS variables. Example: @theme inline { --color-canvas: var(--acme-canvas-color); } This allows theme variables to reference custom properties defined elsewhere.
Tailwind v4 automatic CSS import bundling
Tailwind automatically bundles other CSS files included with @import statements without needing a separate preprocessing tool like Sass or postcss-import. For example, @import "tailwindcss"; and @import "./typography.css"; will have typography.css bundled into the compiled output by Tailwind.
Tailwind v4 uses Lightning CSS for nested CSS processing
Tailwind uses Lightning CSS internally to process nested CSS syntax. Tailwind flattens nested CSS so it can be understood by all modern browsers.
CSS modules require @reference for @theme in Tailwind v4
Each CSS module in Tailwind v4 is processed separately and has no @theme unless one is imported. To use features like @apply in CSS modules, you must explicitly import your global styles as reference using @reference "../app.css"; at the top of the module file.
Tailwind scans source files as plain text, not parsed code
Tailwind treats all source files as plain text and does not parse files as code. It looks for tokens that could be class names based on the characters Tailwind expects in class names. It then tries to generate CSS for all these tokens, discarding any tokens that don't map to a known utility class.
Dynamic class names cannot be detected by Tailwind
Since Tailwind scans source files as plain text, it cannot understand string concatenation or interpolation in programming languages. Class names constructed dynamically, such as `text-{{ error ? 'red' : 'green' }}-600`, will not be detected because the complete class name strings `text-red-600` and `text-green-600` do not exist as literal tokens in the source.
Always use complete class names instead of dynamic construction
To ensure Tailwind detects all required classes, use complete class names statically in your code. For example, instead of constructing `bg-${color}-600`, map component props to complete class name strings like `{ blue: 'bg-blue-600 hover:bg-blue-500', red: 'bg-red-600 hover:bg-red-500' }`. This allows Tailwind to detect all class names at build-time.
Files excluded from Tailwind scanning by default
Tailwind does not scan files in the following cases: files listed in `.gitignore`, files in the `node_modules` directory, binary files like images/videos/zip files, CSS files, and common package manager lock files.
@source directive to explicitly register source paths
Use the `@source` directive in your CSS stylesheet to explicitly register source paths relative to the stylesheet location. For example, `@source "../node_modules/@acmecorp/ui-lib";` registers an external library for scanning. This is useful for scanning dependencies that are in `.gitignore` and ignored by default.
source() function to set base path for source detection
Use the `source()` function when importing Tailwind in CSS to set the base path for source detection explicitly. For example, `@import "tailwindcss" source("../src");` sets the base path to `../src` relative to the CSS file. By default, Tailwind uses the current working directory as its starting point. This is useful in monorepos where build commands run from the monorepo root rather than project roots.
@source not directive to ignore specific paths
Use `@source not` to ignore specific paths relative to the stylesheet when scanning for class names. For example, `@source not "../src/components/legacy";` prevents scanning of the legacy components directory. This is useful for excluding large directories that don't use Tailwind classes.
source(none) to disable automatic source detection
Use `source(none)` when importing Tailwind to completely disable automatic source detection. For example, `@import "tailwindcss" source(none);` allows you to then register all sources explicitly with `@source` directives. This is useful in projects with multiple Tailwind stylesheets where each stylesheet should only include classes it needs.
@source inline() to force generation of specific utilities
Use `@source inline()` to force Tailwind to generate specific class names that may not exist in content files. For example, `@source inline("underline");` ensures the `.underline` class is generated. You can also add variants like `@source inline("{hover:,focus:,}underline");` to generate the underline class with hover and focus variants.
@source inline() supports brace expansion for generating ranges
The `@source inline()` input uses brace expansion, allowing generation of multiple classes at once. For example, `@source inline("{hover:,}bg-red-{50,{100..900..100},950}");` generates all red background colors from 50 to 950 (including 100-900 in increments of 100) with hover variants. This uses bash-style brace expansion syntax.
@source not inline() to exclude specific classes from generation
Use `@source not inline()` to prevent specific classes from being generated, even if they are detected in source files. For example, `@source not inline("{hover:,focus:,}bg-red-{50,{100..900..100},950}");` explicitly excludes all red background utilities and their variants from being generated.
Custom font-feature-settings in @theme
In the @theme block, you can define font-feature-settings for a custom font family using the syntax --font-{name}--font-feature-settings with a value like "cv02", "cv03", "cv04", "cv11".
Custom font-variation-settings in @theme
In the @theme block, you can define font-variation-settings for a custom font family using the syntax --font-{name}--font-variation-settings with a value like "opsz" 32.
@import must come before @import "tailwindcss" for URL imports
When loading fonts from an external URL using @import, the URL import statement must come before @import "tailwindcss" because browsers require @import statements to come before any other rules.
@font-face for custom font loading
Custom fonts can be loaded using the @font-face at-rule with properties for font-family, font-style, font-weight, font-display, and src.
@import directive for CSS
Use the @import directive to inline import CSS files, including Tailwind itself. Example: @import "tailwindcss";
@theme directive for design tokens
Use the @theme directive to define your project's custom design tokens, like fonts, colors, and breakpoints. Design tokens are defined as CSS custom properties within the @theme block, using naming patterns like --font-display, --color-avocado-500, --breakpoint-3xl, and --ease-fluid.
@source directive for explicit content files
Use the @source directive to explicitly specify source files that aren't picked up by Tailwind's automatic content detection. Example: @source "../node_modules/@my-company/ui-lib";
@apply directive for inline utility classes
Use the @apply directive to inline any existing utility classes into your own custom CSS. Example: @apply rounded-b-lg shadow-md; This is useful when you need to write custom CSS but still want to work with your design tokens and use the same syntax as in your HTML.
@reference directive for Vue/Svelte/CSS modules
Use the @reference directive to import your main stylesheet for reference without actually including the styles. This is needed when you want to use @apply or @variant in the <style> block of a Vue or Svelte component, or within CSS modules. Example: @reference "../../app.css"; or @reference "tailwindcss"; when using only the default theme.
Subpath imports in directives
The directives @import, @reference, @plugin, and @config all support subpath imports which work similarly to bundler and TypeScript path aliases, as defined in package.json "imports" field.
--alpha() function for color opacity
Use the --alpha() function to adjust the opacity of a color. Example: color: --alpha(var(--color-lime-300) / 50%); This compiles to: color: color-mix(in oklab, var(--color-lime-300) 50%, transparent);
--spacing() function for spacing values
Use the --spacing() function to generate a spacing value based on your theme. Example: margin: --spacing(4); compiles to: margin: calc(var(--spacing) * 4); This is useful in arbitrary values, especially with calc().
customizing tracking scale with @theme directive
Letter spacing can be customized using the @theme directive. Values are defined as custom properties like --tracking-1, --tracking-2, etc. For example: @theme { --tracking-1: 0em; --tracking-2: 0.025em; --tracking-3: 0.05em; --tracking-4: 0.1em; }
Preflight automatically injected in base layer
When you import tailwindcss into your project, Preflight is automatically injected into the base layer. The default import structure is: @import "tailwindcss/theme.css" layer(theme); @import "tailwindcss/preflight.css" layer(base); @import "tailwindcss/utilities.css" layer(utilities);
Preflight removes all default margins
Preflight removes all default margins from all elements including headings, blockquotes, paragraphs, and others. This is done by setting margin: 0 and padding: 0 on all elements (*, ::after, ::before, ::backdrop, ::file-selector-button) to prevent accidentally relying on margin values from the user-agent stylesheet that are not part of your spacing scale.
Preflight resets border styles
Preflight resets border styles by setting box-sizing: border-box and border: 0 solid on all elements (*, ::after, ::before, ::backdrop, ::file-selector-button). This ensures that adding the border class always adds a solid 1px border using currentColor. This can cause unexpected results when integrating third-party libraries like Google Maps.
Preflight unstyled headings
All heading elements (h1 through h6) are completely unstyled by default with font-size: inherit and font-weight: inherit, making them the same as normal text. This prevents accidentally deviating from your type scale and ensures heading styling is applied consciously and deliberately in UI development.
Preflight unstyled lists
Ordered and unordered lists (ol, ul, menu) are unstyled by default with list-style: none, removing all bullets and numbers. You can style lists using the list-style-type and list-style-position utilities.
Unstyled lists accessibility issue with VoiceOver
Unstyled lists are not announced as lists by VoiceOver. If your content is truly a list but you want to keep it unstyled, add a role="list" attribute to the element so it is properly announced to screen readers.
Preflight makes images block-level
Images and replaced elements (svg, video, canvas, audio, iframe, embed, object) are set to display: block and vertical-align: middle by default. This prevents unexpected alignment issues from the browser default of display: inline. Use the inline utility to override this if needed.
Preflight constrains images to parent width
Images and videos are constrained with max-width: 100% and height: auto, preventing overflow and making them responsive by default while preserving intrinsic aspect ratio. Use the max-w-none utility to override this behavior.
Preflight hidden attribute behavior
Elements with a hidden attribute are enforced to stay invisible using [hidden]:where(:not([hidden="until-found"])) { display: none !important; }. Elements stay hidden unless using hidden="until-found". Remove the hidden attribute entirely to make an element visible to the user.
Extend Preflight with @layer base
To add your own base styles on top of Preflight, add them to the base CSS layer using @layer base in your CSS file. This allows you to define default styles for elements like headings, links, and other base elements.
Disable Preflight by omitting import
To completely disable Preflight, omit the @import "tailwindcss/preflight.css" line while keeping the theme and utilities imports. This is useful when integrating Tailwind into existing projects or when you prefer to define your own base styles.
Override Preflight styles with @layer base
You can work around Preflight styles that conflict with third-party libraries by overriding them with your own custom CSS in the base layer. For example, to fix Google Maps border issues, use @layer base { .google-map * { border-style: none; } }
Preflight based on modern-normalize
Preflight is built on top of modern-normalize and is a set of base styles for Tailwind projects designed to smooth over cross-browser inconsistencies and make it easier to work within the constraints of a design system.
CSS features placement in individual imports
When importing Tailwind CSS files individually, features like source(), theme(), and prefix() should be placed on their respective imports. source(…) and important go on utilities.css, theme(static) and theme(inline) go on theme.css, and prefix(tw) affects both theme.css and utilities.css imports.
Customizing breakpoints with --breakpoint-* theme variables
In Tailwind CSS v4, use --breakpoint-* theme variables in the @theme directive to customize breakpoints. Example: @theme { --breakpoint-xs: 30rem; --breakpoint-2xl: 100rem; --breakpoint-3xl: 120rem; } updates the 2xl breakpoint and creates new xs and 3xl breakpoints.
Removing default breakpoints
To remove a default breakpoint, reset its value to the initial keyword using @theme { --breakpoint-2xl: initial; }. You can also reset all default breakpoints using --breakpoint-*: initial, then define all breakpoints from scratch.
Customizing container sizes with --container-* theme variables
Use --container-* theme variables in the @theme directive to customize container sizes. Example: @theme { --container-8xl: 96rem; } adds a new 8xl container query variant.
@theme directive syntax and purpose
The @theme directive is used to define theme variables that influence which utility classes exist in a Tailwind project. Theme variables are special CSS variables that are more than just regular CSS variables—they also instruct Tailwind to create new utility classes. Theme variables must be defined top-level and not nested under other selectors or media queries. Using @theme makes this requirement explicit and enforceable.
Theme variables vs :root CSS variables
Use @theme when you want a design token to map directly to a utility class. Use :root for defining regular CSS variables that shouldn't have corresponding utility classes. Theme variables do more than regular CSS variables because they instruct Tailwind to create corresponding utility classes.
Design tokens and theme variables definition
Design tokens are low-level design decisions like typography, colors, shadows, and breakpoints. In Tailwind projects, design tokens are stored as theme variables using the @theme directive.
Extending the default theme with new variables
New theme variables can be defined within @theme to extend the default theme. For example, defining --font-script: Great Vibes, cursive; makes a new font-script utility class available. Any new theme variable defined within a namespace automatically generates a corresponding utility class with the same name.
Overriding default theme variables
Override a default theme variable by redefining it within @theme. For example, redefining --breakpoint-sm: 30rem; changes when the sm:* variant triggers. To completely override an entire namespace, use the special asterisk syntax like --color-*: initial; followed by your custom values. When you override an entire namespace, all default utilities in that namespace are removed and only custom values are available.