Skip to content

Create a theme

We start from solid, change the colors, add a paint, then a motion. Each step is rendered next to its code, with the real CSS generated from this site’s configuration file.

The principle: a theme only needs to describe what changes. extends inherits the rest. The options are detailed in the reference.

The shortest theme: it extends solid and only changes its tokens. The colors are semi-transparent to stay readable in both light and dark.

charpente.config.ts
import { defineCharpente } from 'charpente'
export default defineCharpente({
themes: {
brume: {
extends: 'solid',
tokens: {
base: 'rgba(96, 165, 250, 0.3)',
strong: 'rgba(59, 130, 246, 0.55)',
shine: 'rgba(255, 255, 255, 0.6)',
},
},
},
})
Theme brume
Camille DurandProject lead

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

A second theme extends the first and redefines the paint of three leaf types. Only the given fields are replaced: here, the radius of media and controls and the background of blocks.

charpente.config.ts
themes: {
'brume-ronde': {
extends: 'brume',
paint: {
media: { style: { borderRadius: '50%' } },
control: { style: { borderRadius: '999px' } },
block: {
fill: 'repeating-linear-gradient(45deg, var(--charpente-base) 0 10px, var(--charpente-strong) 10px 20px)',
},
},
},
}
Theme brume-ronde
Camille DurandProject lead

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

A theme can also react to the pointer. This one extends brume, gives each type a hover and pauses the animation under the pointer. Hover a shape to see it. Hover needs an area without inert, as in this demo.

charpente.config.ts
themes: {
'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 survol-finHover a shape
Camille DurandProject lead

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

For a plain color change, hover: 'accent' is enough: a token name or a color paints every type. Hover is ignored on React Native.

Media and blocks are larger than text, so their pattern is usually drawn about 15% bigger. In the short format, fill, size, radius and every style value accept a function of the step: 1 for text and controls, scale (default 1.15) for media and blocks.

themes/grille/theme.ts
import { defineTheme } from 'charpente'
export default defineTheme({
colors: ['#dbeafe', '#93c5fd'],
pattern: {
fill: 'linear-gradient(var(--charpente-strong) 1px, transparent 1px), linear-gradient(90deg, var(--charpente-strong) 1px, transparent 1px)',
size: (step) => `${8 * step}px ${8 * step}px`,
},
radius: (step) => `${2 * step}px`,
style: { boxShadow: 'inset 0 0 0 1px #93c5fd' },
paint: { text: { style: { borderRadius: '1px' } } },
})
Theme grille
Camille DurandProject lead

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

The grid is 8px on text and controls and 9.2px on media and blocks. style adds declarations to every type. An explicit paint still wins for the type it names, and its style is merged into the expanded one.

A border can be an image. Put an SVG next to theme.ts and point border at it: the file is read at build time and drawn with border-image on every skeleton, text and blocks alike. slice is the size of the corners in the SVG, width the thickness drawn, repeat how the edges fill (round by default).

variants derives several themes from one: each key becomes the theme <name>-<variant> and replaces the fields it gives. The Christmas recipe keeps one gift border and offers several wrapping papers:

recipes/christmas/theme.ts
export default {
...paper('#fecaca', '#b91c1c', '#fca5a5'),
border: { image: './border.svg', slice: 30, width: '14px' },
variants: {
green: paper('#bbf7d0', '#15803d', '#86efac'),
gold: paper('#fde68a', '#a16207', '#fcd34d'),
snow: paper('#e0f2fe', '#0369a1', '#bae6fd'),
},
} satisfies ThemeDef
Theme christmas
Camille DurandProject lead

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

Theme christmas-green
Camille DurandProject lead

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

The SVG is an ordinary file: edit it and the stylesheet is regenerated. schedule and include use the derived names, such as christmas-green.

A motion is a CSS animation applied to every leaf. It starts after delay + rank × stagger, so every motion follows the stagger of the site. The animated properties are free.

charpente.config.ts
motions: {
rebond: {
duration: '1.2s',
keyframes: {
'0%, 100%': { transform: 'translateY(0)' },
'50%': { transform: 'translateY(-3px)' },
},
},
}
<div
data-charpente-loading
data-charpente-theme="brume-ronde"
data-charpente-motion="rebond"
aria-busy="true"
inert
>
...
</div>

With a wrapper: <Charpente loading theme="brume-ronde" motion="rebond">.

brume-ronde + rebond
Camille DurandProject lead

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

Make it the default theme, or assign it to a period of the year with the seasonal calendar.

charpente.config.ts
export default defineCharpente({
defaultTheme: 'brume-ronde',
motion: 'rebond',
})

The optional charpente-studio package gives you a live editor: npx charpente-studio, tune the colors, pattern and radius on a skeleton card, then save. It writes the same short-format themes/<name>/theme.ts file described above. Borders and variants are still edited by hand.