@skirbi/pinta

3 min

@skirbi/pinta is Skirbi’s CSS theme and token engine. It supplies color families, derived backgrounds, typography, and recipes for Semtic components. It needs no JavaScript runtime.

Pinta removed its general-purpose utility attributes in v0.0.12. Spacing, borders, shadows, and responsive layout belong in your CSS. Use classes or attributes as selectors, whichever makes the intent clearer.

Load and activate

Load the stylesheet exported as @skirbi/pinta/pinta.css. For a plain HTML site, serve that file and link to it:

html
<link rel="stylesheet" href="/css/pinta.css">

Enable the theme on the page or a subtree:

html
<html skirbi-theme="mytheme">
html
<section skirbi-theme="mytheme">...</section>

An empty skirbi-theme uses Pinta’s defaults. A name is a CSS selector hook; it does not load a separate theme. Define your named theme in your stylesheet. Semtic elements keep their semtic- names.

Cascade layers

css
@layer skirbi-base, skirbi-theme, skirbi-transform, skirbi-components,
  skirbi-app;
Layer Purpose
skirbi-base Structural defaults, typography, focus styling
skirbi-theme Default tokens and theme-author overrides
skirbi-transform Derived colors and final tokens
skirbi-components Component recipes
skirbi-app Application overrides

Load your theme CSS after Pinta. Put token overrides in skirbi-theme and application rules in skirbi-app. Later layers win for normal declarations; within a layer, specificity and source order still matter. Unlayered normal CSS takes precedence over layered normal CSS.

Color families

Each of primary, secondary, tertiary, and quaternary has main, soft, strong, and contrast tokens. Start with the main colors:

css
@layer skirbi-theme {
  [skirbi-theme="mytheme"] {
    --skirbi-primary-main: #127872;
    --skirbi-secondary-main: #fcb919;
    --skirbi-tertiary-main: #fad68d;
    --skirbi-quaternary-main: #243642;
    --skirbi-secondary-contrast: var(--skirbi-on-light);
  }
}

Pinta mixes each main color with white and black to derive soft and strong. Contrast colors use foreground helpers; they are not calculated contrast checks. Choose readable foregrounds for your palette.

The older --skirbi-*-light and --skirbi-*-dark tokens remain deprecated aliases for soft and strong.

Status colors are separate: --skirbi-info-base, --skirbi-success-base, --skirbi-warn-base, and --skirbi-error-base.

Page and surface colors

Source tokens feed Pinta’s background derivations:

css
@layer skirbi-theme {
  [skirbi-theme="mytheme"] {
    --skirbi-page-bg-source: var(--skirbi-primary-main);
    --skirbi-surface-bg-source: var(--skirbi-tertiary-main);
    --skirbi-wash-perc: 8%;
  }
}

The page defaults to a wash. Choose another transform on the theme root:

html
<html skirbi-theme="mytheme" skirbi-bg-transform="reverse-wash">

Supported values are none, wash, and reverse-wash. The attribute selects the transform percentage; setting only the --skirbi-bg-transform custom property does not switch it.

For an exact background, set --skirbi-page-bg or --skirbi-surface-bg. Application recipes should consume --skirbi-page-bg-final, --skirbi-surface-bg-final, and --skirbi-surface-border-final so those overrides are respected.

Light and dark mode

Put the mode on the same element as theme activation:

html
<html skirbi-theme="mytheme" skirbi-theme-mode="dark">

Light is the default. Pinta switches modes through the theme attribute. Toggle it between light and dark to update the themed subtree:

js
document.documentElement.setAttribute('skirbi-theme-mode', 'dark');

Dark mode changes the environment used for mixing, foregrounds, and surface sources. Theme-specific overrides use the same selector:

css
@layer skirbi-theme {
  [skirbi-theme="mytheme"][skirbi-theme-mode="dark"] {
    --skirbi-page-fg: #f8fafc;
    --skirbi-surface-bg-source: #243642;
    --skirbi-surface-fg: #f8fafc;
  }
}

Typography and controls

css
@layer skirbi-theme {
  [skirbi-theme="mytheme"] {
    --skirbi-font-sans: system-ui, sans-serif;
    --skirbi-font-scale: 1.125;
    --skirbi-line-height: 1.7;
    --skirbi-h1-scale: 2.5;
    --skirbi-prose-gap: 1.5rem;
    --skirbi-field-height: 3rem;
    --skirbi-button-radius: 0.5rem;
  }
}

--skirbi-field-height sizes text inputs and selects. Checkboxes and radios use a proportion of it, unless --skirbi-choice-size is set. Textareas keep their own height. Focus styling uses --skirbi-focus-ring-size and --skirbi-focus-ring-color.

Component recipes and application CSS

Buttons express their role through attributes:

html
<button type="submit" lead>Save</button>
<button type="button" support>Cancel</button>
<button type="button" antagonist>Delete</button>

Other button roles are extra and herald. Each has --skirbi-button-<role>-* tokens. These are component recipes, not a generic tone/variant system for arbitrary elements.

For your own styling, use ordinary CSS:

html
<semtic-panel class="login-card">
  ...
</semtic-panel>
css
@layer skirbi-app {
  [skirbi-theme] .login-card {
    max-inline-size: 28rem;
    padding: 1.5rem;
    border: 1px solid var(--skirbi-surface-border-final);
    border-radius: 0.75rem;
    background: var(--skirbi-surface-bg-final);
    color: var(--skirbi-surface-fg);
  }
}

Pinta also supplies defaults for semtic-grid, semtic-flex, and semtic-stack, plus named grid recipes such as diptych and triptych. See Semtic layout elements.

See the theme specification for the contract. The detailed recipe reference lives in the DocumentCSS blocks in Pinta’s lib/themes/pinta.css.