Skip to content

Search

Start typing to search.


Show all pages

Documentation

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:

TypeScript
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

Terminal
bunx calaca skin build skins/acme.ts -o src/styles/acme.css

The 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:

CSS
@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:

Terminal
bunx calaca skin build skins/acme.ts -o public/acme.css --prebuilt

Generated 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:

TypeScript
"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.
TypeScript
"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.