Modular imports
Actual CSS has two kinds of entrypoint. Without /css, you get a compiled artifact ready to serve. With /css, you compose the sources.
Entrypoints
Composable source CSS (recommended)
actual-css/css core
actual-css/css/<family> one family, e.g. css/layout, css/forms/all
actual-css/css/<family>/* one module, e.g. css/components/button
actual-css/css/full every family, as source
Zero-config distribution
actual-css complete compiled framework
actual-css/full the same bundle, named explicitly
actual-css/css contains the shared baseline: reset, tokens, theme, document defaults, intents, universal variants, focus, and print.
It contains no typography, layout, forms, components, effects, or utilities.
The full framework starts from the core and adds every family in cascade order:
core → typography → layout → forms → components → effects → utilities
Focus is a core invariant
The core provides a visible :focus outline as the compatibility baseline. Modern browsers limit it to :focus-visible, hiding the pointer-focus outline.
Components may enhance or replace that treatment in interactive states, but their base styles must not cancel the shared fallback.
When an interactive state replaces the outline, it must preserve a visible focus indicator in forced-colors, either with an outline-based fallback or by keeping the replacement out of forced-colors.
Start from the full entry
Deleting is easier than adding, and it keeps the cascade order right. Copy the package's full entry into your project, rewrite its relative imports to package paths, then delete what you do not need:
- CSS:
src/css/actual.full.css. Prefix each import withactual-css/css/and drop.cssand/index;./actual.cssisactual-css/css. To go below family level, replace a family line with the imports of itsindex.css, rewritten the same way. - JS:
src/js/full.js. Prefix each import withactual-css/js/and drop.js;./index.jsisactual-css/js.
/* app/index.css — actual.full.css, rewritten, components expanded and pruned */
@import "actual-css/css";
@import "actual-css/css/typography";
@import "actual-css/css/layout";
@import "actual-css/css/forms/all";
@import "actual-css/css/components/button";
@import "actual-css/css/components/close";
@import "actual-css/css/components/card";
@import "actual-css/css/components/modal";
@import "actual-css/css/utilities";
// app/index.js — full.js, rewritten and pruned
import "actual-css/js/flyout";
import "actual-css/js/dialog";
import "actual-css/js/dismiss";
import "actual-css/js/tooltip";
import "actual-css/js";
JS modules import what they depend on. CSS modules do not, and three are
shared: keep components/close with modal, drawer, alert, or a removable
badge; components/bleed with a .bleed child of card, drawer, or accordion;
components/spinner with components/busy.
Replace a component, keep the conventions
Actual CSS components are reference implementations, not mandatory dependencies. Omit a component's stylesheet and write a smaller implementation when your application needs fewer features. Keep its semantic markup, state conventions, and shared theme tokens where they fit.
Choose the level of customization that matches the need:
- Change a color, size, or spacing through the component's public hooks.
- Give the component a different appearance with an application CSS variant.
- Replace a component that does more than you need by omitting its module and writing your own implementation.
Example: a simpler Steps component
The built-in Steps component supports horizontal and vertical layouts, connectors, optional labels, responsive representations, scrolling, and focus for navigable labels. A static, horizontal progress indicator can use the same HTML with a smaller stylesheet.
Import only the framework modules the application uses, followed by the replacement:
/* app.css */
@import "actual-css/css";
@import "actual-css/css/components/button";
@import "actual-css/css/components/card";
@import "./steps.css";
Omit actual-css/css/components/steps and any entrypoint that includes it:
actual-css, actual-css/full, actual-css/css/full, or
actual-css/css/components. Loading the original and overriding it retains
its weight and may introduce cascade conflicts.
Keep the ordered list, .step-complete for completed steps, and
aria-current="step" for the current step:
<ol class="steps steps-horizontal" aria-label="Booking progress">
<li class="step-complete">
<span class="step-label">Details</span>
</li>
<li aria-current="step">
<span class="step-label">Payment</span>
</li>
<li>
<span class="step-label">Confirmation</span>
</li>
</ol>
The application owns this replacement stylesheet:
/* steps.css — static horizontal progress only */
.steps-horizontal {
--step-size: 2rem;
counter-reset: app-step;
display: flex;
gap: var(--space-30);
margin: 0;
padding: 0;
list-style: none;
}
.steps-horizontal > li {
counter-increment: app-step;
flex: 1 1 0;
min-inline-size: 0;
display: grid;
justify-items: center;
align-content: start;
gap: var(--space-20);
text-align: center;
color: var(--text-muted);
}
.steps-horizontal > li::before {
content: counter(app-step);
display: grid;
place-items: center;
box-sizing: border-box;
inline-size: var(--step-size);
block-size: var(--step-size);
border: 2px solid var(--border);
border-radius: var(--radius-full);
background: var(--surface-opaque);
color: var(--text-muted);
font-weight: var(--font-weight-semibold);
}
.steps-horizontal > .step-complete::before {
background: var(--state-selected);
border-color: var(--state-selected);
color: var(--state-selected-fg);
}
.steps-horizontal > [aria-current="step"] {
color: var(--text);
}
.steps-horizontal > [aria-current="step"]::before {
border-color: var(--state-selected-text, var(--state-selected));
color: var(--state-selected-text, var(--state-selected));
}
.steps-horizontal .step-label {
color: inherit;
font-size: var(--font-size-sm);
overflow-wrap: anywhere;
}
The shared tokens supply theme colors, typography, and spacing without copying the palette. This recipe is for static labels: use the built-in Steps for navigable labels, connectors, vertical orientation, or responsive representations.
If a replacement needs different markup or semantics, give it an application-specific component name.
Family manifests
Each family is one import. The contents are enumerations rather than table cells, so they read as a list:
- Core —
actual-css/cssreset, tokens, theme, base, intents, variants, focus, print - Typography —
actual-css/css/typographyprose, overline, fluid - Layout —
actual-css/css/layoutstack, cluster, center, frame, media, switcher, sidebar-layout, grid, scroller, scroll-snap, topbar, container-query, app-shell - Forms —
actual-css/css/formsnative control baseline - Forms (complete) —
actual-css/css/forms/allbase plus input-icon, switch, range, choice-card, custom-select, floating-field, otp - Components —
actual-css/css/componentsbuttons, surfaces, overlays, feedback, navigation, data and composed controls - Effects —
actual-css/css/effectsaura, shimmer - Utilities —
actual-css/css/utilitiesbase and extra utilities
.form-actions composes with .cluster: a build that takes forms without the
layout family also imports actual-css/css/layout/cluster, in any order.
Individual modules can be imported through their domain path:
@import "actual-css/css/forms/otp";
@import "actual-css/css/components/chat";
@import "actual-css/css/effects/aura";
@import "actual-css/css/layout/scroller";
@import "actual-css/css/typography/fluid";
@import "actual-css/css/utilities/extra";
Use your existing CSS pipeline
Actual CSS is plain CSS and does not require its own build tool.
If a project already uses Vite, PostCSS, Webpack, Parcel, Lightning CSS, or another CSS pipeline, keep using it and import the Actual CSS package exports directly.
For example:
@import "actual-css/css";
@import "actual-css/css/layout";
@import "actual-css/css/forms/all";
@import "actual-css/css/components/button";
@import "actual-css/css/components/card";
@import "./theme.css";
@import "./app.css";
The consuming pipeline remains responsible for features such as asset handling, URL rebasing, source maps, CSS Modules, import modifiers, transpilation, and browser targeting.
Actual CSS intentionally does not require a particular bundler or transpiler.
Optional CLI bundler
For projects without a CSS pipeline, Actual CSS includes a small zero-dependency import flattener:
npx actual-css bundle src/app.css --out public/app.css
or:
bunx actual-css bundle src/app.css --out public/app.css
Add --minify for lightweight whitespace/comment minification:
npx actual-css bundle src/app.css --out public/app.css --minify
The CLI resolves and inlines:
@import "./local.css";
@import "actual-css/css/layout";
@import "actual-css/css/components/card";
It deliberately does not transpile CSS. Modern syntax such as nesting, @container, @scope, :has(), color-mix(), light-dark(), and media-query range syntax is preserved for the browser.
The CLI is a simple flattener, not a general-purpose CSS build pipeline.
Limitations
Local and package imports may be plain imports, or carry a layer modifier:
@import "./local.css";
@import "./local.css" layer(components);
@import "./local.css" layer;
A layered import is flattened into the block it stands for, so @import "actual-css/css" layer(actual) becomes @layer actual { … }. A layer declared inside the imported file becomes a sublayer of that wrapper, exactly as the import did.
Conditional modifiers are not flattened:
@import "./local.css" supports(display: grid);
@import "./local.css" screen;
@import "./local.css" layer(components) supports(display: grid);
Use an existing CSS pipeline when these features are required.
One further case is refused rather than guessed: a remote import inside a layered subtree, because hoisting it before ordinary rules would move it out of its layer.
The CLI also does not process or rebase asset URLs such as:
background-image: url("../images/card.svg");
Those paths remain exactly as written, so projects with relative assets should generally let their existing build pipeline handle CSS imports.
Remote and absolute imports are preserved rather than fetched and are moved before ordinary rules so they remain valid CSS.
Cascade layers
Actual CSS can participate in a project-owned cascade layer:
@layer actual;
@import "actual-css/full" layer(actual);
Or, when composing families:
@layer actual;
@import "actual-css/css" layer(actual);
@import "actual-css/css/typography" layer(actual);
@import "actual-css/css/layout" layer(actual);
@import "actual-css/css/forms/all" layer(actual);
@import "actual-css/css/components" layer(actual);
actual-css/css/layer also exposes the minimal core wrapped in @layer actual.
All of these recipes flatten with actual-css bundle: the CLI understands layer modifiers, so a public entrypoint of the framework is never something its own bundler has to refuse.
Custom properties
Component-prefixed custom properties documented on component pages are author hooks.
Color and sizing otherwise stay within the shared intent, variant, control, and button APIs.