Colors, fonts and logo
Your brand lives in one file, src/content/singletons/brand.json. In Keystatic it’s the
Brand screen (in Handy under Pages → Brand). The brand is shared by every language, and
it stays a file in your project even when the rest of your content lives in Sanity or EmDash,
because fonts are chosen when the site is built.
Handy’s brand:
{ "accent": "#f26a1b", "second": "#ffd23f", "third": "#6fb6e8", "corners": "round", "fonts": { "body": "Figtree", "heading": "Bricolage Grotesque" }}The fields
Section titled “The fields”| Field | What it does in Handy | Allowed values |
|---|---|---|
accent |
Hi-vis orange: buttons, marks, the ticks on the to-do list | A hex color with six digits, e.g. #f26a1b |
second |
Safety yellow: the to-do note, the price tag, the tape measure | Hex color |
third |
Painter’s tape blue: a second fill and the tape under marked words | Hex color |
corners |
How round cards and buttons are | round or soft |
fonts.body |
The text font | One of the text fonts below |
fonts.heading |
The heading font | One of the heading fonts below |
The exact list for your template is the brand part of src/content/model.ts; the Brand screen
in Keystatic shows the same fields with their descriptions. Templates differ here:
- Most templates have
accent,secondandthird. Toon adds afourth; Encore has two inks,misregistrationandgrain; Chalk has atextureswitch. - Aurora uses
accent,secondary,radius(sharp,soft,round) andbuttons(pillorrounded). Lava picks a ready-madepaletteorcustomwithground,accentandsecondary. - Many templates have a third font:
mono(code, times or prices),hand(handwriting) orlabel.
Colors must be written as # plus six hex digits. Anything else (#fff, orange) is refused by
the editor and by the build with “Use a hex color like #d6402e”.
Change it
Section titled “Change it”In Keystatic: with npm run dev running, open /keystatic, then Brand. Pick or type the
colors, choose the corners and fonts, and click Save. Colors and corners show on the next
reload.
In the file: edit src/content/singletons/brand.json in your editor and save. The result is
the same.
Fonts are the exception: they’re set up when the site starts. After changing a font, stop
npm run dev (Ctrl C) and start it again.
How every color stays readable
Section titled “How every color stays readable”You choose a few colors; the template works out all the others for both themes in
src/lib/brand.ts and checks each pair against the WCAG AA contrast rules. In Handy:
| Derived color | Made from | Rule |
|---|---|---|
| Buttons | The accent, darkened | White button text reaches 4.5:1 |
| Links and accent-colored text | The accent’s hue | 4.5:1 against the page, cards and sunken panels, in each theme |
| Marks (ticks, icons, lines) | The accent | 3:1 against the backgrounds (the rule for graphics) |
Fills (second, third) |
Your second and third colors, lightened | Dark text on them reaches 4.5:1 |
| Secondary text, borders, the night board | Fixed shades of the template | Checked against the backgrounds the same way |
So the colors on the page aren’t always exactly the ones you typed: with Handy’s #f26a1b, the
buttons come out a slightly darker orange, #c3520c, so their white text is readable. The
lightness changes; the hue stays yours.
When the build stops
Section titled “When the build stops”If no shade of a color can pass, the build (and npm run dev) stops with a message like:
Brand: no readable button color (white text on the accent) for these colors. Pick a different color in brand.json.The message names the color that failed: the button, the link color in one theme, the second
or third color, and so on. To fix it, pick a different color for that field in brand.json and
save. With Handy’s way of deriving colors this is rare: the template keeps moving the lightness
until the pair passes, and only stops if it reaches black or white without passing.
A typo stops it too: Brand: "#12345" is not a hex color like #1626a8.
Templates whose text sits directly on the accent (Toon and Slab, for example) need a light enough accent; the field’s description in the Brand screen tells you what each color is used for and which way it gets adjusted.
Handy offers:
Text font (fonts.body) |
Heading font (fonts.heading) |
|---|---|
| Figtree, Work Sans, Manrope, DM Sans, Barlow, Atkinson Hyperlegible | Bricolage Grotesque, Archivo Black, Rubik, Bowlby One, Darker Grotesque, Sora |
Each template has its own list, chosen to fit its look: BODY_FONTS and HEADING_FONTS (and
MONO_FONTS or HAND_FONTS where there’s a third font) at the top of src/content/model.ts.
How fonts are loaded
Section titled “How fonts are loaded”Fonts come from Fontsource through the Astro Fonts API, set in
astro.config.mjs from brand.json:
fonts: [ { name: brand.fonts.body, cssVariable: '--font-body', provider: fontProviders.fontsource(), weights: [400, 700], styles: ['normal'], subsets: ['latin'], fallbacks }, { name: brand.fonts.heading, cssVariable: '--font-heading', provider: fontProviders.fontsource(), weights: [HEADING_WEIGHTS[brand.fonts.heading] ?? 800], styles: ['normal'], subsets: ['latin'], fallbacks },],- The font files are downloaded when the site is built and served from your own site: no requests to a font service, nothing to declare in a cookie banner.
- The text font loads in two weights (400 and 700), the heading font in one.
- Astro adds a fallback font with matched metrics, so the page doesn’t jump when the font arrives.
- Only the
latincharacter set is included. If you add a language that needs more characters (for example Czech or Polish, which needlatin-ext), add that subset to both lines.
Heading weights
Section titled “Heading weights”Each heading font is drawn in one weight, listed in HEADING_WEIGHTS:
export const HEADING_WEIGHTS: Record<string, number> = { 'Bricolage Grotesque': 800, 'Archivo Black': 400, Rubik: 800, 'Bowlby One': 400, 'Darker Grotesque': 900, Sora: 800 };The list is in two places, astro.config.mjs (which weight to download) and src/lib/brand.ts
(which weight the headings use). Keep them the same. Aurora and Lava don’t have this list.
Use a font that isn’t in the list
Section titled “Use a font that isn’t in the list”- Check that the font is on Fontsource and note its name exactly as Fontsource writes it, and, for a heading font, which weight you want.
- Add the name to
BODY_FONTSorHEADING_FONTSinsrc/content/model.ts. - For a heading font, add
'Font Name': weighttoHEADING_WEIGHTSin bothastro.config.mjsandsrc/lib/brand.ts. - Put the name in
brand.json(or pick it in the Brand screen) and restartnpm run dev. - Add the font and its license to
LICENSES.mdif you keep one for your site.
Day and night themes
Section titled “Day and night themes”Every template has two themes, and every color is derived and checked for both. The visitor
switches with the toggle in the header; the choice is remembered in their browser (theme in
local storage) and set before the page is drawn, so there’s no flash.
Which theme opens first depends on the template: Handy always opens in the day theme (a pale pegboard), even when the visitor’s system is dark, and the toggle switches to the night (a charcoal board). Your template’s page in these docs says which it uses.
You don’t set the night colors yourself: they come from the same brand.json. In the code, the
theme is the data-theme attribute on <html> (dark or light), and the colors are CSS
variables (--brand-bg, --brand-text, --brand-link…) that src/lib/brand.ts writes into
every page’s head. src/styles/brand.css only holds defaults.
Corners
Section titled “Corners”corners sets two CSS variables, the corner radius of cards and panels and the radius of
buttons. In Handy, round is 1rem (buttons 0.8rem) and soft 0.4rem. Other templates offer
other values (square, sharp, rounded): the Brand screen lists them.
Logo and favicon
Section titled “Logo and favicon”The logo is drawn in code, not an image file:
src/components/Logo.astro: a small SVG mark (in Handy, a wrench on a hi-vis block) next to your site’s name from Site settings → Name, linking to the home page. The mark uses the brand’s colors, so it follows your accent and both themes.src/pages/favicon.svg.ts: the same mark as the browser-tab icon, drawn at build time in your brand’s colors and served at/favicon.svg.
Changing the name in Site settings is enough for most sites. To use your own mark:
- Replace the
<svg>insrc/components/Logo.astrowith yours. Keeparia-hidden="true"on it (the link already has a label for screen readers) and color it with the theme variables (var(--brand-button),var(--brand-ink)…) rather than fixed colors, so it works in both themes. - Put the same drawing in
src/pages/favicon.svg.ts. It reads the colors fromcolors.light(fromsrc/data/brand.ts), because a favicon can’t use CSS variables.
Prefer an image file? Put it in src/assets/ and show it with Astro’s <Image> in
Logo.astro; for the favicon, put the file in public/ and change the
<link rel="icon"> line in src/layouts/Base.astro.
The browser’s address bar color (theme-color) also comes from the brand: it’s the day theme’s
background.
Related
Section titled “Related”- Make it yours: the map of what to change where
- Working in the code: using theme colors in your own components