@skirbi/pinta
v0.0.49
Published
Skirbi's theme song
Readme
@skirbi/pinta
Skirbi's theme song
Pinta is the theme + token engine for Skirbi.
- Semtic provides semantic structure and components
- Pinta provides tokens, derivations, and component recipes
- Dibuho provides higher-level composition/layout patterns
Pinta is intentionally small.
It is not:
- a utility framework
- a Tailwind replacement
- a Bootstrap clone
- a layout engine
Pinta follows the 80% rule:
- sensible defaults
- clean tokens
- predictable recipes
- easy overrides with normal CSS
Everything is CSS. No runtime. No JS required.
General-purpose utility attributes were removed in v0.0.12. Use classes or meaningful attributes as selector hooks, then let CSS handle spacing, borders, shadows, and responsive layout.
Installation
npm install @skirbi/pintaImport the stylesheet from the package:
@import "@skirbi/pinta/pinta.css";Philosophy
Pinta is built around:
color families
-> derivations
-> component recipesInstead of:
utility attributes
-> inline compositionYou are encouraged to use:
- semantic HTML
- Semtic components
- normal CSS classes
Example:
<semtic-panel login-card>semtic-panel[login-card] {
max-width: 28rem;
}Or if you prefer classes:
<semtic-panel class="login-card">.login-card {
max-width: 28rem;
}Pinta intentionally embraces native CSS instead of competing with it.
Opt-in
Pinta is explicit opt-in.
Nothing happens unless [skirbi-theme] exists.
Whole page:
<html skirbi-theme>Subtree:
<section skirbi-theme>Named theme:
<html skirbi-theme="voodoo">Why Skirbi and not Pinta?
The namespace skirbi- is chosen over pinta- because @skirbi/dibuho also
paints the canvas and is part of the skirbi-universe. Having one namespace
makes things easier.
Cascade layers
Pinta defines stable layer order:
@layer skirbi-base, skirbi-theme, skirbi-transform, skirbi-components,
skirbi-app;Meaning:
skirbi-base- structural defaults
- typography
- accessibility defaults
skirbi-theme- palette
- tokens
- theme overrides
skirbi-transform- color derivations and background transforms
skirbi-components- light component recipes for Semtic elements
skirbi-app- application-specific overrides
Theme authors should override in:
@layer skirbi-theme {
[skirbi-theme="foo"] {
}
}For normal declarations, later layers take precedence over earlier layers. Specificity and source order still resolve competing declarations within a layer. Unlayered normal CSS takes precedence over layered normal CSS.
Theme modes
Pinta supports:
<html skirbi-theme skirbi-theme-mode="light">
<html skirbi-theme skirbi-theme-mode="dark">Dark mode changes:
- environment base
- page and surface source colors
- surface derivation lifts
- default foregrounds
Mode can be applied to any subtree.
Pinta provides light/dark switching through skirbi-theme-mode. Toggle the
attribute between light and dark; Pinta updates the environment, source
colors, foregrounds, and surface derivations for that subtree.
const root = document.documentElement;
const currentMode = root.getAttribute('skirbi-theme-mode');
root.setAttribute(
'skirbi-theme-mode',
currentMode === 'dark' ? 'light' : 'dark',
);Color families
Pinta uses color families instead of loose palette slots.
Primary
--skirbi-primary-main
--skirbi-primary-soft
--skirbi-primary-strong
--skirbi-primary-contrastSecondary
--skirbi-secondary-main
--skirbi-secondary-soft
--skirbi-secondary-strong
--skirbi-secondary-contrastUsually only *-main needs to be set.
Pinta derives:
- soft
- strong
unless explicitly overridden.
Contrast defaults to either --skirbi-on-dark or --skirbi-on-light. It is a
selected foreground, not a calculated contrast check, so theme authors must
verify contrast for their palette.
The older --skirbi-*-light and --skirbi-*-dark properties remain as
deprecated aliases for soft and strong.
Supporting colors
Helper colors exist for supporting accents.
--skirbi-tertiary-main
--skirbi-tertiary-soft
--skirbi-tertiary-strong
--skirbi-tertiary-contrast
--skirbi-quaternary-main
--skirbi-quaternary-soft
--skirbi-quaternary-strong
--skirbi-quaternary-contrastTertiary and quaternary expose the same complete family contract as primary and secondary.
Button tokens
--skirbi-button-radius
--skirbi-button-lead-bg
--skirbi-button-lead-color
--skirbi-button-lead-hover-bg
--skirbi-button-support-bg
--skirbi-button-support-color
--skirbi-button-support-border
--skirbi-button-extra-bg
--skirbi-button-extra-color
--skirbi-button-extra-border
--skirbi-button-antagonist-bg
--skirbi-button-antagonist-color
--skirbi-button-antagonist-borderVisual tokens
--skirbi-divider-image /* background-image for semtic-divider */Background transforms
Pinta derives atmospheric backgrounds from brand colors.
Transform modes
<html skirbi-theme skirbi-bg-transform="none">
<html skirbi-theme skirbi-bg-transform="wash">
<html skirbi-theme skirbi-bg-transform="reverse-wash">Wash controls
--skirbi-wash-perc: 8%;
--skirbi-reverse-wash-perc: 85%;Default page backgrounds use a wash transform.
This keeps themes calm and readable by default.
The selector attribute changes the active percentage. Setting only
--skirbi-bg-transform does not switch the transform.
For exact backgrounds, set --skirbi-page-bg, --skirbi-surface-bg, or
--skirbi-surface-bg-elevated. Component and application CSS should consume
the matching *-final tokens so explicit overrides and derived fallbacks both
work.
Status colors
Status colors are semantic, not branding.
--skirbi-info-base
--skirbi-success-base
--skirbi-warn-base
--skirbi-error-baseThey are intentionally opinionated for:
- readability
- contrast
- colorblind resilience
It is strongly advised not to override them without proper testing and validating assumptions.
Typography
Typography is opt-in and token-driven.
Core knobs
--skirbi-font-scale
--skirbi-line-height
--skirbi-h1-scale
--skirbi-h2-scale
--skirbi-h3-scale
--skirbi-h4-scale
--skirbi-prose-gapFont families
--skirbi-font-sans
--skirbi-font-monoForm control tokens
One token sizes a field:
--skirbi-field-heightIt is the height of a text input and of a select, and the checkboxes and radios take 2/3.5 of it, so they sit well inside the height of the field beside them rather than reading as a big empty box. A field is tall mostly through padding, which a choice control does not have, so a ratio near 1 would track padding the box is not carrying. Set it once and all four follow.
[skirbi-theme] {
--skirbi-field-height: 3.5rem;
}Unset, it works out to the padding, border and line-height of the shared control recipe, which is what these controls have always measured. The textarea is left out on purpose: it is running text sized by its rows.
Two knobs remain for the exceptions:
--skirbi-choice-size
--skirbi-field-line-height--skirbi-choice-size sizes the choice controls directly, for when 2/3.5 of
the field is not what you want. Either way the result lands in
--skirbi-choice-size-final, which is what the tick and the radio dot are
scaled from, so they follow the box whichever way it was set.
Invalid fields
A field reads as wrong in two cases, drawn the same way:
[semtic-error]holds a message- the browser's own constraint validation fails, e.g.
pattern,required,type,min/max
The second uses :user-invalid, not :invalid, so the browser holds the
styling back until the field has been left or a submit has been attempted. A
half-typed value is not marked wrong on the second keystroke, and a required
field is not red before it has ever been filled in.
The control border, its focus ring and the label take the error colour in both
cases. Only a real message also gets the host its wash and padding, because
that is what label-error-position lays out and what changes the size of the
field: a native invalid state stays inside the box it already has, so nothing
moves as the user tabs out of it.
--skirbi-error-baseNote that constraint validation only runs on a native form submission. A form
submitted from script, e.g. a framework binding on a button, never triggers it,
so :user-invalid there only fires once a field has been left.
Component recipes
Pinta ships light recipes for Semtic elements.
Examples:
semtic-panelsemtic-tilesemtic-terminalsemtic-navsemtic-sidenavsemtic-tablesemtic-aside
These recipes are intentionally conservative and easy to override.
Layout primitives
Semtic ships minimal layout primitives:
<semtic-grid>
<semtic-flex>
<semtic-stack>These are structural primitives, not utility APIs.
Use normal CSS for:
- spacing
- layout tuning
- responsiveness
- composition
Pinta provides light defaults only.
The plain recipes expose --skirbi-columns, --skirbi-grid-gap,
--skirbi-flex-gap, and --skirbi-stack-gap. semtic-flex retains its
component-specific semtic-direction, semtic-justify, semtic-align, and
semtic-wrap="wrap" mappings. There is no numbered semtic-gap mapping or
responsive semtic-columns-* API.
Pinta also provides semtic-grid[diptych] and semtic-grid[triptych]
recipes. Their shared layout thresholds are 48rem and 69rem.
Example:
<semtic-grid player-details>Use normal CSS for semtic-grid[player-details] to define columns.
Accessibility defaults
Pinta ships default focus handling:
--skirbi-focus-ring-size
--skirbi-focus-ring-colorFocus styling is theme-level because it affects accessibility.
Native CSS is the escape hatch
Pinta intentionally prefers normal CSS over utility APIs.
Example:
<semtic-panel class="featured">.featured {
border-color: var(--skirbi-primary-main);
}This is the preferred extension model. Whether you pick class based vs attribute based is up to you as the author.
Documentation source
lib/themes/pinta.css is the source of truth for the detailed style
documentation.
The stylesheet uses DocumentCSS-style comment blocks for the public theme contract:
@pagefor the Pinta overview and layer documentation@stylesfor token groups, cascade layers, transforms, and Semtic recipes- Markdown tables and examples inside the comment body
The README stays the human-facing overview. Generate the detailed style guide
with npm run docs.
Example documentjs.json shape:
{
"sites": {
"styles": {
"glob": "lib/**/*.{css,md}",
"dest": "styleguide"
}
}
}Removed utilities
Pinta v0.0.12 removed built-in utility attributes from the core package.
Core Pinta intentionally stays focused on:
- tokens
- derivations
- recipes
- theming
