@skirbi/pinta

2 min

@skirbi/pinta defines how things look.

It is the token and primitive theme layer for semtic. Everything is CSS — no runtime, no JS required.

Opt-in

Pinta does nothing unless [semtic-theme] is present. Apply it to the whole page or scope it to a subtree:

html
<html semtic-theme>...</html>
html
<div semtic-theme="mytheme">...</div>

Named themes are supported. The attribute value selects which theme overrides apply.

Cascade layers

Pinta fixes layer order so overrides are always predictable:

css
@layer pinta-base, pinta-theme, pinta-components;
  • pinta-base — engine, tokens, primitives, interactions
  • pinta-theme — palette, pointers, defaults
  • pinta-components — light defaults for semtic elements

Write your overrides in pinta-theme or pinta-components. You always come last, so you always win.

Theme mode

html
<html semtic-theme semtic-theme-mode="dark">

Toggle via JS:

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

Tokens

Brand palette

Override the four base color slots:

css
@layer pinta-theme {
  [semtic-theme] {
    --semtic-primary-base:    #1AB1AB;
    --semtic-secondary-base:  #FCB919;
    --semtic-tertiary-base:   #FAD68D;
    --semtic-quaternary-base: #127872;
  }
}

Pinta derives light and dark variants automatically.

Pointers

Pointers decide which color drives which surface:

css
@layer pinta-theme {
  [semtic-theme] {
    --semtic-page-tone-base:    var(--semtic-tertiary-base);
    --semtic-surface-tone-base: var(--semtic-quaternary-base);
  }
}

Status colors

Used for info, success, warn, error states. Override carefully and test for colorblind resilience.

css
--semtic-info-base:    #2f6fed;
--semtic-success-base: #0f766e;   /* teal, not neon green */
--semtic-warn-base:    #d97706;   /* orange, not pale yellow */
--semtic-error-base:   #b91c1c;

Typography

Driven by knobs:

css
@layer pinta-theme {
  [semtic-theme] {
    --semtic-font-scale:  1.125;
    --semtic-line-height: 1.7;
    --semtic-h1-scale:    2.5;
    --semtic-h2-scale:    1.875;
    --semtic-prose-gap:   1.5rem;
  }
}

Optional font profile presets:

html
<div semtic-theme semtic-font-profile="editorial">

Primitives

These attributes work on any element inside a themed subtree.

Attribute Values Effect
semtic-pad 0–6 Padding
semtic-gap 0–6 Gap (for layout containers)
semtic-border 0–5 Border width
semtic-radius 0–6 Border radius
semtic-shadow 1–5 Box shadow
semtic-shadow-border 1–5 Shadow-based border
semtic-bg Surface background
semtic-hover Hover interaction
semtic-selected Selected state
semtic-press Press interaction
semtic-disabled Disabled state

Example card:

html
<article semtic-bg semtic-pad="3" semtic-radius="4" semtic-shadow="2" semtic-hover semtic-press>
  ...
</article>

Tone and variant

Apply color context to any element:

html
<button semtic-theme-tone="primary" semtic-theme-variant="solid">Save</button>
<div semtic-theme-tone="warn" semtic-theme-variant="outline">Warning</div>

Tones: primary, secondary, tertiary, quaternary, info, success, warn, error.

Variants: solid, outline, gradient.

Dark mode overrides

css
@layer pinta-theme {
  [semtic-theme="mytheme"][semtic-theme-mode="dark"] {
    --semtic-page-tone-base: var(--semtic-primary-base);
    --semtic-page-fg:        var(--semtic-tertiary-base);
    --semtic-page-opacity:   95%;
  }
}

Interaction knobs

css
@layer pinta-theme {
  [semtic-theme] {
    --semtic-hover-shadow:        0 8px 16px rgb(0 0 0 / 0.08);
    --semtic-press-shadow:        0 2px 4px rgb(0 0 0 / 0.12);
    --semtic-transition-duration: 200ms;
    --semtic-hover-ring-size:     0px;
  }
}

Overriding components

css
@layer pinta-components {
  semtic-panel {
    /* your overrides */
  }
}