Skip to content

Configuration

Everything is set in charpente.config.ts. defineCharpente only types the object.

charpente.config.ts
import { defineCharpente } from 'charpente'
export default defineCharpente({
defaultTheme: 'solid',
timeZone: 'Europe/Paris',
schedule: [{ theme: 'christmas', from: '12-15', to: '12-31' }],
motion: ['pulse', 'shimmer'],
timing: { delay: 120, stagger: 40 },
colorScheme: {
dark: '[data-theme="dark"]',
tokens: {
light: { base: '#e5e7eb' },
dark: { base: '#2a2d34' },
},
},
themes: {
brume: {
extends: 'solid',
tokens: { base: 'rgba(96, 165, 250, 0.28)' },
paint: {
media: { style: { borderRadius: '50%' } },
},
},
},
motions: {
rebond: {
duration: '1.2s',
keyframes: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-3px)' },
},
},
},
})
Option Type Default Role
defaultTheme string 'solid' Theme of areas without data-charpente-theme.
schedule ScheduleEntry[] [] Seasonal slots, see the calendar.
timeZone string (IANA) runtime time zone Time zone used to read today’s date in schedule.
motion string or string[] 'shimmer' Default motion(s). 'none' disables.
timing Timing { delay: 120, stagger: 0 } Default delay and offset.
reveal number 0 Delay in ms between two nested levels when content appears after loading. 0 fades the first level only. Local setting with --charpente-reveal.
initial boolean or number off Vite only. Serves the page already marked as loading, so no content flashes before your scripts run. The page is released when your code takes over, or that many ms after the load event (true means 3000). Needs inline scripts to be allowed by your CSP.
scope 'element' or 'page' 'element' Where the wrappers and setLoading put the loading state by default: on their own element, or on the whole page (<html>). Local setting with the scope prop.
themesDir string 'themes' Folder scanned for themes: each subfolder with a theme.ts file is declared under its folder name.
shapes Record<string, ShapeDef> {} Layered shapes to register by name for paint.shape, see layered shapes.
colorScheme ColorScheme system media query Dark mode and per-mode tokens.
themes Record<string, ThemeDef> {} Adds themes, or replaces a built-in theme of the same name.
motions Record<string, MotionDef> {} Adds motions, or replaces a built-in motion.
Key Default Role
delay 120 ms Wait before the skeleton appears. Overridable with --charpente-delay.
stagger 0 ms Delay added per rank among siblings. Overridable with --charpente-stagger.

Without dark, dark tokens apply through @media (prefers-color-scheme: dark). With dark, they apply to the element matching the selector, for example the html element carrying data-theme="dark".

colorScheme: {
dark: '[data-theme="dark"]',
tokens: { dark: { base: '#1f2937', strong: '#374151' } },
}
Key Type Role
dark CSS selector Replaces the media query with a selector.
tokens.light Tokens Overrides light-mode tokens, set on :root.
tokens.dark Tokens Overrides dark-mode tokens.

A theme has these optional fields: extends, tokens, paint, hover, hoverMotion and motion (the name of the motion it runs by default). With extends, the theme inherits the parent’s tokens, paints and hover: tokens are merged, paints are merged type by type, and within style key by key. fill and size replace the parent’s.

Built-in theme names in theme: work with the Vite plugin and the CLI, which build the CSS in Node and ship no JavaScript. In a browser or React Native, compile and nativeTheme come from charpente/lite, which only knows solid: import the themes you use from charpente/themes, one export each, and the bundler drops the rest.

import { compile } from charpente/lite
import { cyberpunk } from charpente/themes
const { css } = compile({ themes: { cyberpunk }, defaultTheme: cyberpunk })

A name that is not imported throws an error that points to charpente/themes. Measured with esbuild, minified:

Bundle Size Gzip
compile from charpente (18 themes) 24.2 kB 7.7 kB
compile from charpente/lite with one theme 14.6 kB 5.7 kB
nativeTheme from charpente 14.8 kB 4.3 kB
nativeTheme from charpente/lite 4.2 kB 1.8 kB
One theme alone from charpente/themes 0.5 kB 0.3 kB

--charpente-* CSS variables usable in fill and style. A token defined in a theme applies to areas of that theme, whether the mode is light or dark.

Token Light Dark Usage
base #e5e7eb #2a2d34 Main color of the skeleton.
strong #c4c8d0 #454a55 Outlines, patterns, subtle accents.
shine rgba(255, 255, 255, 0.7) rgba(255, 255, 255, 0.08) Glint color of shimmer.
accent #0891b2 #22d3ee First decoration color.
accent2 #c026d3 #e879f9 Second decoration color.

A --charpente-radius variable (0.35em) is also set on :root, used by solid for its corners.

A “leaf” is an element that is painted as a single piece. paint assigns a paint to each of the four types:

Type Elements concerned
text Any element without child elements (a paragraph, a heading, a link, a cell), except media and controls.
media img, svg, video, canvas, picture, iframe.
control button, input, select, textarea.
block Elements marked data-charpente-block: the element is painted as one block and its children are hidden.
Key Type Role
fill string Comma-separated background layers (background-image). Default: a flat var(--charpente-base) fill.
size string Size of each layer, in the same order (background-size). auto for an unspecified layer.
position string Position of each layer (background-position), 0 0 by default. A layer can use calc(var(--charpente-flow) * 96px) to move with an ambient motion.
repeat string Repeat of each layer (background-repeat), repeat by default. no-repeat pins a layer such as a moon in a corner.
style Record<string, string> Added CSS declarations: borderRadius, clipPath, outline, or an --x variable.
Custom paints
themes: {
stripes: {
extends: 'solid',
paint: {
text: {
fill: 'repeating-linear-gradient(to bottom, var(--charpente-base) 0 0.8lh, transparent 0.8lh 1lh)',
style: { borderRadius: '2px' },
},
media: {
fill: 'radial-gradient(circle, var(--charpente-strong) 1.4px, transparent 2px), linear-gradient(var(--charpente-base), var(--charpente-base))',
size: '8px 8px, auto',
},
},
},
}

hover changes the look of a skeleton shape while the pointer is over it. It is set per leaf type, like paint, and only the types you name change. The fields are those of a paint (fill, size, style) plus radius and border, which take the same values as in the short format.

Key Type Role
hover string or per type A token name ('strong', 'accent'…) or a color paints every type with it. An object gives fill, size, style, radius and border per type.
hoverMotion 'pause', 'faster' or 'none' What the animation does under the pointer: stop, run faster (--charpente-hover-fast, default 600ms), or nothing. Default none.
motion string The motion used by default for this theme when the area has no data-charpente-motion, for instance crt. Inherited with extends.
Custom hover
themes: {
survol: { extends: 'solid', hover: 'accent' },
'survol-fin': {
extends: 'brume',
hover: {
text: { fill: 'linear-gradient(var(--charpente-accent), var(--charpente-accent))', radius: '999px' },
media: { border: '3px solid var(--charpente-accent)' },
control: { style: { opacity: '0.6' } },
block: { fill: 'linear-gradient(var(--charpente-accent2), var(--charpente-accent2))' },
},
hoverMotion: 'pause',
},
}
Theme survolHover a shape
Camille DurandProject lead

A short introduction spanning two or three lines, to show how text is painted line by line.

Details:

  • The rules apply to the automatic mode and to the faithful mode: the captured zones (data-charpente-capture) react as a whole, and their text and media layers take the hover paint.
  • They sit inside @media (hover: hover), so touch screens never see them. The hovered shapes receive pointer events, and an inert area never receives any: leave inert off the area (keep aria-busy) when you want hover, knowing that a loading link or button under the pointer can then be clicked. The framework wrappers always set inert, so hover is for hand-written markup for now.
  • The change is eased over --charpente-hover-ms (default 150ms). The easing is dropped when the user prefers reduced motion.
  • With extends, hover is merged type by type and key by key like paint, and hoverMotion is inherited.
  • React Native has no hover: nativeTheme ignores both fields.
Key Default Role
duration required Duration of one cycle, for example '1.4s'.
easing 'ease-in-out' CSS timing function.
iterations 'infinite' Number of cycles.
layer none Background layer overlaid on the leaves, used by shimmer for its glint.
keyframes required Animation steps: from, to or percentages, each with its declarations.
container none Keyframes applied to the loading container next to the leaf keyframes, as glitch does.
overlay none A layer drawn over the loading container: style for its static look, duration and keyframes for its movement, which stops under reduced motion. Used by crt.
A custom motion
motions: {
blink: {
duration: '900ms',
easing: 'steps(2, end)',
iterations: 'infinite',
keyframes: {
from: { opacity: '1' },
to: { opacity: '0.4' },
},
},
}

The compiler fails with an explicit message in these cases:

  • an unknown theme in defaultTheme, extends or schedule;
  • a theme that extends itself, directly or in a loop;
  • an unknown motion in motion;
  • a schedule date that is not in MM-DD form;
  • an unknown time zone in timeZone.

It warns, without failing, when two default motions animate the same property: the last one wins.

Variable Role
--charpente-delay Local delay, with unit (200ms).
--charpente-stagger Local offset, with unit.
--charpente-base, -strong, -shine, -accent, -accent2 Tokens, overridable on any element of the area.
--charpente-radius Radius of solid.
--charpente-hover-ms Duration of the hover transition, with unit (default 150ms).
--charpente-hover-fast Cycle duration with hoverMotion: 'faster' (default 600ms).