Skins
Dress the core for your product, step by step. Extend the neutral base skin or start from the bare skeleton.
What a skin does
The core gives every component its structure, behavior and accessibility: the layout, the focus ring, the touch target, the disabled state. A skin dresses it: radius, border, shadow, height, padding, type, color and motion.
A skin is a TypeScript file that maps each Calaca class to Tailwind utilities.
The calaca command turns it into CSS. Because a skin is data, a skin that
extends another one lists only what differs, and its values always win.
1. Choose a starting point
- Extend
base. The neutral, complete skin. You change only what makes your product different. This is the right choice for most products. - Start bare. You dress every class yourself, from the skeleton up. Choose
this when your product looks nothing like
base.
2. Write the skin
Create skins/acme.ts in your project:
import { base, defineSkin } from "@calaca/ui";
export default defineSkin(
"acme",
{
"ca-button": "rounded-full font-semibold",
"ca-button--md": "h-11 px-6",
"ca-card": "rounded-3xl shadow-lg",
},
base,
{
light: {
"--ca-action-primary": "oklch(52.92% 0.1104 163.97)",
"--ca-action-primary-hover": "oklch(59.13% 0.1231 164.29)",
"--ca-text-on-action": "oklch(100% 0 0)",
},
dark: {
"--ca-action-primary": "oklch(78.8% 0.1233 166.2)",
"--ca-action-primary-hover": "oklch(83.69% 0.1062 167.77)",
"--ca-text-on-action": "oklch(21.98% 0.0298 168.55)",
},
},
);defineSkin takes the skin’s name, its classes, the skin it extends and its
token values. Leave out base to start bare. A class can take one string or a
list of strings, one per concern; later strings win over earlier ones.
The name must be lowercase letters, digits and dashes. Unknown tokens fail
right away, and the build fails on a class Calaca does not define, so a typo
such as ca-buton never ships unnoticed.
3. Build it
bunx calaca skin build skins/acme.ts -o src/styles/acme.cssThe output is Tailwind source: the tokens, then one rule per class with the base lines first and yours last. Import it after the core, in place of a built-in skin:
@import "tailwindcss";
@import "@calaca/ui/core.css";
@import "./acme.css";For a page without a Tailwind build, add --prebuilt. The output then holds
the core, your tokens and every class as finished CSS, like the prebuilt
files in Installation:
bunx calaca skin build skins/acme.ts -o public/acme.css --prebuiltGenerated files start with a “Do not edit” comment. Change the .ts file and
build again. The command runs on Bun for now.
4. Keep the split
The core and a skin never set the same CSS property. The core owns position, display, focus outline, pointer events and the touch target; the skin owns how things look. When both set one property, which one wins depends on Tailwind’s sort order instead of your intent. The built-in skins are tested against this rule; for your own skin, stay on the look side.
Variants work the same way. A component paints itself once from pigments:
--ca-pigment (fill), --ca-pigment-ink (text), --ca-pigment-edge (border)
and their -hover forms. A variant such as ca-button--primary only sets
those variables. To restyle a variant, set its pigments:
"ca-button--secondary": "[--ca-pigment:var(--color-muted)] [--ca-pigment-ink:var(--color-foreground)]",5. Adapt at runtime
A skin can respond to the visitor with Tailwind variants, inside any class:
dark:for the dark appearance (tokens usually cover this already).motion-safe:for transitions and animation, so reduced motion stays still.interact:for hover on devices that hover and for keyboard focus together.pointer-coarse:for touch screens, for example a taller control.forced-colors:for Windows high contrast.
"ca-button": [
"rounded-full",
"motion-safe:transition-[background-color,scale] motion-safe:duration-150 active:scale-[0.97]",
],6. Check it
Open your pages in light and dark, with the keyboard only and at 200% zoom. Text needs a contrast of 4.5:1 against its background, and the focus ring and control borders 3:1. The Accessibility guide lists what the core guarantees and what a skin must keep.