-
Notifications
You must be signed in to change notification settings - Fork 0
Design tokens
Colour, type, spacing and layout widths, defined once and emitted as CSS custom properties into the head of both the public page and the editor preview, from the same layout.
That last part is the point. If the editor and the front end read different sources for colour and spacing, the preview lies, and a preview that lies is worth less than no preview at all.
Defaults live in Safi\Atelier\Tokens:
| Group | Keys | Default |
|---|---|---|
color |
primary |
#171717 |
on-primary |
#ffffff |
|
text |
#171717 |
|
muted |
#737373 |
|
surface |
#ffffff |
|
border |
#e5e5e5 |
|
font |
sans |
ui-sans-serif, system-ui, sans-serif |
arabic |
"Noto Sans Arabic", "Segoe UI", Tahoma, sans-serif |
|
space |
section |
6rem |
gutter |
1.5rem |
|
width |
container |
80rem |
prose |
48rem |
Deliberately small. A token nothing renders is a token nobody maintains, so the set grows when a block needs one rather than in anticipation.
config/atelier.php:
'tokens' => [
'color' => [
'primary' => '#0f766e',
'on-primary' => '#ffffff',
],
'font' => [
'arabic' => '"IBM Plex Sans Arabic", sans-serif',
],
],Overrides merge group key by group key, so changing one colour does not mean restating the other five. Anything you leave out keeps its shipped value, which also means an install that predates this config key gets the full palette without republishing anything.
<style>
:root{--atelier-color-primary:#0f766e;--atelier-color-text:#171717; ...}
[dir="rtl"]{--atelier-font-sans:var(--atelier-font-arabic)}
body{font-family:var(--atelier-font-sans)}
</style>Inline rather than a stylesheet, because the whole block is under a kilobyte and a separate request would cost more than it saves. It also cannot go stale, which a published file can.
The Arabic swap rides on [dir="rtl"] rather than a locale code, so adding a third RTL
language gets it for free.
Two ways, and the second is the one that matters.
Directly in CSS, when the block always uses that value:
<section {{ $shared->class(['px-6']) }} style="padding-block: var(--atelier-space-section)">As a stored reference, when the client chooses:
Select::make('background')
->options(Tokens::options('color'))
->formatStateUsing(fn (mixed $state) => is_array($state) ? ($state['token'] ?? null) : $state)
->dehydrateStateUsing(fn (?string $state) => $state ? ['token' => $state] : null)The tree then holds a reference, not a literal:
"background": { "token": "color.primary" }and the renderer resolves it to var(--atelier-color-primary) before your view runs. Your
view just echoes $attributes['background'] into a style attribute and never learns that
tokens exist.
This is why the shared background control stores a reference: change color.primary in
config and every page that picked it restyles, with no data migration.
use Safi\Atelier\Tokens;
Tokens::all(); // the merged set, defaults plus your overrides
Tokens::css(); // the :root block, as a string
Tokens::value('color.primary'); // 'var(--atelier-color-primary)', or null if unknown
Tokens::options('color'); // ['color.primary' => 'Primary', ...] for a Select
Tokens::resolve(['token' => '...']); // reference to CSS value, anything else passes throughTokens::value() returns null for a key nobody defined, rather than emitting a variable
that resolves to nothing.
Tokens ship in atelier::partials.tokens, and it has to come after your stylesheet so
the custom properties win:
@vite(['resources/css/app.css'])
@include('atelier::partials.tokens')Leave it out and nothing errors. Every var(--atelier-*) resolves to nothing, so the
background and spacing controls silently stop doing anything and Arabic loses its font
stack. See Layouts.
They are not a theme system, and they are not exposed to the client. Nobody picks a font size in the panel. The developer sets the palette, the client picks from it where a block offers a choice, and the site stays coherent because there was never a control that could break it.
Building
Running a site
Reference