Foundations
The parts everything else is made of: the mark, the colour, the type,
the space, the motion and the pattern. UNIÓN, Beckenham Place Park, Friday 9 July 2027.
Every value on this page is read live from css/tokens.css, so what you see
here is what a component gets.
The U and the N are joined. The wordmark is drawn as one continuous inflated stroke, so the first and last letters close the word into a loop rather than ending it.
The Ó is a character. The accented O steps out of the word, puts on headphones and dances. It is the only figure in the system.
The U, the I and the N, cut from the wordmark and stacked, make the soundwave. It is drawn from the same letterforms as everything else, so it is the icon, and it also works as a meter, a pin, a bullet and an equaliser.
Anatomy, tokens, behaviour, a11y, markup
Anatomy
Three observations, not three claims. Each one is shown with the real asset beside it, so a reader can check the statement against the drawing.
Tokens used
.t-microfor the numbering--s6grid gap
When NOT to use
This is documentation, not a component. Nothing on a live surface says what the identity means; it just is it.
Every real mark, on the ground it is drawn for


Clear space , one cap height of the Ó on every side, and nothing inside it
Minimum sizes, shown at their real size
Sticker, 150px. Below this LONDON closes up.


Anatomy, tokens, behaviour, a11y, markup
Anatomy
Two lockups (horizontal and portrait stack), one outline cut, one sticker. The stack exists in four ground pairings; the horizontal in one, recoloured by the ground.
Ground pairings
- Black mark on cream, pink, coral or yellow
- Cream mark on black, coral or pink
- Never a cream or yellow mark on cream, coral or pink
Asset trap, read this
The four UNION_STICKER-*.svg files and
UNION_LOGO-horizontal-cream-pink-outline.svg are BROKEN: the stickers
carry a hole through the field and the outlined cut is cropped. They have been
deleted from assets/svg/. Use the 4x PNGs in
assets/img/brand/ through an <img> with width, height
and alt. <u-svg> logs an error if you ask it for one.
Accessibility
A mark that carries meaning gets real alt text naming the festival. A mark that
repeats a heading beside it is decorative and gets alt="".
<u-svg> marks its own SVG aria-hidden.
When NOT to use
Never set the wordmark in Archivo. It is a drawing, not type; there is no font that produces it. Where the word must be live text, it is set in the display voice and is not the mark.
Copy this
<u-svg src="UNION_LOGO-horizontal-primary"></u-svg>
<u-svg src="UNION_LOGO-stack-portrait-black" fill="var(--cream)"></u-svg>
<!-- the sticker and the outlined cut are PNGs, never <u-svg> -->
<img class="d-sticker" style="--mark:240px"
src="assets/img/brand/UNION_STICKER-union-london-black-on-pink@4x.png"
width="1125" height="654" alt="UNIÓN London sticker">Three paths, three bars. The system's only icon, because it is the only one the identity actually draws. Never stretched to fit a space: it repeats at its native proportion instead.
The four supplied colourways
Anatomy, tokens, behaviour, a11y, markup
Anatomy
Three separate paths, so each bar can be animated independently. That is what makes it a working equaliser rather than a picture of one.
Behaviour
As a meter or equaliser it is the .d-wave device, documented on
02 The seven devices. Verb: BOUNCE.
When NOT to use
Below 40px, and never stretched on either axis. Where a small mark is needed, repeat the glyph at native proportion or use a drawn rule instead.
Copy this
<u-svg src="icon:black"></u-svg> <u-svg src="icon:black" fill="var(--cream)"></u-svg>
The soundwave's animation is part of the
identity, not a decoration applied to it. The icon is three drawn paths, the U, the I and
the N cut and set side by side at three different heights, so each is a squiggle rather
than a bar. Measured off the artwork: the strokes sit at x 0, 110 and 168 in a 327 by 198
box, which is why each one pivots on its OWN CENTRE and not on a common floor. Scaling
them from the bottom flattens the drawing into a smear. The glyph's overall box is never
scaled, X is left alone, and the amplitudes stay small, so it breathes rather than
stretches. Five modes, one API: UNION.wave(el, mode).
The three bars stand up one after another, on the bounce ease. The logo's load animation.
A continuous equaliser. Each bar scales on Y from its own baseline at its own tempo, squashy rather than linear, so the three never lock into one pulse. Runs only while something is genuinely playing.
A slow live-light breath, for an alert bar. Says "this is live" without asking to be watched.
The bars fill to a value from 0 to 1 and hold, reading at slightly different heights
the way a real level meter does. UNION.wave(el, 'meter', {value:.7})
Everything stops at a readable level, never flat: a flat wave reads as broken. This
is also what every mode becomes under prefers-reduced-motion.
The 40px floor still holds when it moves. Below it the three bars merge and the motion reads as a flicker.
Anatomy, tokens, behaviour, a11y, markup
The API
UNION.wave(el, mode, opts) where el is the
<u-svg> and mode is one of 'intro'
'eq' 'pulse' 'meter' 'rest'.
It returns the GSAP timeline, and calling it again on the same element replaces
whatever was running. UNION.wave.stop(el) kills it.
How it transforms
Each path gets transform-box:fill-box and
transform-origin:50% 50%, so it pivots on its own centre inside its own
fill box. The paths are sorted left to right by bounding box first, because paint
order is not reading order and a stagger should run across the glyph the way an eye
does.
Only scaleY moves, and only between about.58 and 1.3. Rest is exactly
1, the glyph as drawn. Past roughly 1.3 the strokes stop reading as the mark.
Reduced motion
Every mode resolves to rest: the bars sit at a readable, uneven level
and nothing loops.
In the chrome
The small soundwave beside the sidebar wordmark runs intro on load and
eq while the brand block is hovered, so the documentation itself uses
the identity's own animation.
When NOT to use
Below 40px. On anything with no level to report: eq on a static page is
the icon pretending. Use rest and let it simply be the mark.
Copy this
<u-svg id="wave" src="icon:black"></u-svg>
UNION.wave(wave, 'intro'); // the load animation
UNION.wave(wave, 'eq'); // playing
UNION.wave(wave, 'pulse'); // a live alert
UNION.wave(wave, 'meter', {value:.7}); // a level, 0..1
UNION.wave(wave, 'rest'); // stop, at a readable level
UNION.wave.stop(wave);Large or not at all
It is a character with a body and a gesture, not a glyph. At icon size the headphones close up and the accent reads as a smudge, so the system gives it a floor of 150px and no ceiling. One per surface.
Anatomy, tokens, behaviour, a11y, markup
Tokens used
.d-mascotwith--smor--lg--dur-bounce/--ease-verb-bounce
Behaviour
Verb: BOUNCE, because it dances. .is-dancing loops; it stops under
prefers-reduced-motion and it stands still.
Its grounds
Pink, coral, cream. Never black, never yellow (the headphones are yellow and vanish). Its body is the warm near-black, so
on a black ground it disappears and all that is left is a pair of headphones and a
floating accent. There is deliberately no dark-ground modifier on
.d-mascot.
When NOT to use
Never on black. Never on a form field, never as a bullet, never repeated across a grid, never below 150px. It is the one place the brand is allowed to be charming, so spending it on decoration wastes it.
Copy this
<u-svg class="d-mascot" src="UNION_MASCOT-o-acute-dancing-headphones"></u-svg>
--pinkThe primary. The call to action, the label bar, Main Stage.--coralThe Grove. The second accent, never the CTA.--yellowThe Corner, and every welfare and safety surface.--creamThe ground. Never white.--blackThe ink and the slab. Never #000; it is a warm brown-black.The stage system.
[data-stage] sets --stage for everything inside it.
--stage-main--stage-grove--stage-cornerMeasured contrast, computed in the browser from the tokens. Three verdicts: any text, display only, never. Only the never pairs are struck through.
| Text on ground | Sample | Ratio | Body 17px | Display 44px+ | Verdict |
|---|
Anatomy, tokens, behaviour, a11y, markup
The rule, in three verdicts
- Any text. Black on cream, pink, coral or yellow. Cream on black.
- Display only. Cream on pink (3.16:1) and cream on coral (2.27:1), which are brand pairs used on the stack logo, the tote and the stickers. Also pink
or yellow on black. These carry marks,
.t-mega,.t-display,.t-headand button labels at 18px bold and up. Never.t-body,.t-smallor.t-micro. - Never. Yellow or cream on cream. Yellow on pink or coral. Pink on coral.
What "display only" actually forbids
Information someone NEEDS never goes on a display pair: set times, prices, directions, gate numbers, welfare and safety. Those are always black on a light ground, or cream on black. The display pairs are for the mark and the shout, which is what they do beautifully in the mockups.
Tokens: --on-pink-display and --on-coral-display, both
cream, so a component states that it knows it is using a display pair.
How this table is built
JavaScript reads the five colour tokens off the computed style, converts to relative
luminance and computes the WCAG ratio. Nothing is typed in, so the table cannot drift
from tokens.css.
Hairlines
A hairline may use black at 25% alpha (--hairline). That is a rule
weight, not a sixth colour, and it never carries text.
When NOT to use
No tints, no shades, no sixth colour, no gradient between two of them. If a surface needs another value, it needs a different composition.
Archivo variable, one family, two axes.
Width is an axis (font-stretch), never a separate family: there is no
"Archivo Expanded" in this system. Latin-ext is loaded so the Ó is real.
Together.
A festival in south London parkland, ninety-six acres of it, for 11,500 people across three stages. Doors 2:30pm, curfew 10:30pm. Lineup to confirm.
The Ó, at every weight the system uses
The 10x ratio. Display to body, at the same scale the posters run it.
Beckenham Place Park, London. Doors 2:30pm, curfew 10:30pm. 11,500 capacity. Three stages.
Anatomy, tokens, behaviour, a11y, markup
Tokens used
--font,--wdth-condensed,--wdth-wide--size-megathrough--size-small--track-micro
The ratio
The posters run roughly ten times the body size for the headline. A page where the headline is only two or three times the body has drifted into a UI kit, whatever the colours say.
Accessibility
Every voice is set in rem-safe clamps and none is below 11px. All-caps
display text gets its real casing in the markup where it is read out, so a screen
reader does not spell it.
When NOT to use
Never .t-mega twice on one surface. Never .t-wide for a
paragraph. Never a script or handwriting font: the aside is a drawing.
Copy this
<h1 class="t-display">Music. People. Together.</h1> <span class="t-micro">Music · people · culture · together</span> <p class="t-body">…</p>
Space , nine steps. Nothing between them.
Shape , three radii, and each one belongs to a named kind of object.
Rules and hairlines
Focus , 3px solid, offset 3px. Yellow on a dark ground.
Anatomy, tokens, behaviour, a11y, markup
Tokens used
--s1to--s9,--gutter--radius-slab/--radius-card/--radius-round--rule,--rule-heavy,--hairline--focus-width,--focus-offset,--tap-min
Breakpoints
390 phone · 768 tablet · 1100 the sidebar threshold · 1440 the desktop shot width.
They live as comments in tokens.css, because a custom property cannot be
used inside a media query.
When NOT to use
No uniform radius across everything: that is the single loudest AI tell, and the one the mockups never commit. A square thing stays square.
One verb per component, chosen because the object DOES that. A stub tears because a stub tears. Bars bounce because they are a meter. Nothing fades in on scroll just to arrive. Press Replay on any of them.
The object arrives and settles on its own springiness.
--dur-bounce 420ms · --ease-bounce
The bubble lettering's own compression, scaleY.6 to 1. The
brand's core gesture. --dur-squash 360ms
Rotate and translate ALONG the perforation, the way a stub
comes off. --dur-tear 520ms
Scale 1.6 to 1 with a rotation and an overshoot, like a rubber
stamp landing. --dur-stamp 260ms
Reveal ALONG the length, the way a stroke is drawn.
--dur-draw 700ms · --ease-out
Colour spreads OUT from the tag that names it, by clip-path.
--dur-flood 380ms
From above, on a bounce, the way the signage flags fall in.
--dur-drop 480ms
Rotate on a FIXING, elastic, the way a fingerpost blade hangs.
--dur-swing 900ms
Anatomy, tokens, behaviour, a11y, markup
The API
Every verb is UNION.motion.<verb>(element, options). Each reads its
duration and easing from tokens.css, returns the GSAP tween so it can be
chained, and honours prefers-reduced-motion by jumping to the final
state with no motion.
Reduced motion
Every component shows its final state and any auto-running loop stops: marquee,
equaliser, river drift, the mascot's dance. Marquees otherwise run slow, 90 to 120
seconds a loop (--dur-marquee).
When NOT to use
Never two verbs on one component. Never a fade as the mechanism: a fade is what you reach for when you have not decided what the thing does.
Copy this
UNION.motion.tear(stub);
UNION.motion.drop(flags, {stagger:.06});
UNION.motion.flood(slabFill, {from:'left'});
UNION.motion.draw(pathEl); // an SVG path draws along its length
UNION.motion.swing(blade, {origin:'50% 0%'});This is not solved. The pattern work
across the project is still falling short of the mockups, and what follows is offered as
observation rather than law. It is judged by eye against the source mockups and by measuring, not by rule. Wide drawn pattern artwork may be supplied, which is why the
.pattern-slot below takes its image from a variable: new artwork drops in
without a rebuild.
In the meantime: use pattern sparingly. A component that works without it goes without it. Most sections on these two pages carry none at all, on purpose.
The preferred treatment , the river seen through a window that fades at its edges (the waiting room). This is the approved treatment. Prefer a fade into the ground over any hard pattern edge.
The strokes overlap into one mass along the track, each at its own scale and angle, the ground breaking through between them. The window is taller than the tallest stroke, so nothing is cut top or bottom, and both ends dissolve. Nothing stops on a straight line.
What has been measured so far. Observations, beside what the mockups actually do. Where our render and the mockup disagree, the mockup wins.
Measured so far, all of it
provisional: a river runs 12 to 28 strokes, each 9 to 14% of its host's width, stepped
tight down one diagonal. Ink budget somewhere around 7 to 20% of a composition. A ribbon
bleeds off an edge or ends in its own round cap. Pattern does not sit behind live text.
The three UNION_PATTERN tiles are not seamless, so they are not tiled.
Pattern all over is fine as the SKIN of a physical object, which is where the mockups use
it most: the cup, the wristband, the lanyard, the flag.
The ribbon library. Each stroke at its own proportion, on the ground it is drawn for. The p3 cream strokes (06, 07, 10, 15, 16) are for dark grounds only.
Anatomy, tokens, behaviour, a11y, markup
.pattern-slot , the swappable band
One element whose pattern comes from either a CSS variable or a
UNION.river() call, with the edge fade applied as a mask. When wide drawn artwork is supplied it drops straight in:
--pattern-imageany image, repeated along X at its own height--pattern-heightthe band's height, default 150px (measured on K4)--pattern-fadehow far the fade reaches in from each edge, default 10%--yfades on the Y axis instead, for a tall band.pattern-slot__runthe K4 stroke run, for the generated form
Because the slot owns the fade, a lane never has to decide how a pattern band ends. That was the decision most often got wrong.
The river API
UNION.river(host, opts) generates a river. It is not markup, so it
cannot be pasted in as a decorative block. Each stroke is a .rv-s
carrying --i and --n for staggered motion.
Tokens (provisional)
--ink-budget-min7% /--ink-budget-max20%--river-stroke-min9% /--river-stroke-max14% of the HOST's width--river-count-min12 /--river-count-max28
These record what has been measured, not a settled standard.
Measured notes
sizeis a fraction of the HOST, not the page. Set it per breakpoint.- A river needs WIDTH. Wide and shallow reads as a river; narrow and tall reads as a blob, whatever the count.
- On a dark ground the same settings read thinner: raise
count, tightenspread. fadeover a dark ground reads as a grey glow, a banned tell. Prefer bleeding off the host edge.
The one place it goes all over
As the SKIN of a physical object: a cup, a wristband, a lanyard, a bag, a bottle, a flag. That is a thing wearing the pattern, not a sample of it, and it is where the mockups use pattern most confidently. See the applications gallery below.
Still weak
The generated river reads thinner and more scattered than the drawn bands, which are a fatter, more continuous mass. Held next to the mockup sheets the difference is obvious. Until that closes, prefer the fading window, use less than feels right, and leave a section without pattern rather than give it a weak one.
Copy this
<!-- drawn artwork drops straight in -->
<div class="pattern-slot" style="--pattern-image:url(assets/img/band.png)"></div>
<!-- or the generated form, the K4 run inside the same fading window -->
<div class="pattern-slot">
<span class="pattern-slot__run" aria-hidden="true">
<u-svg src="ribbon:p3-15" fill="var(--pink)"></u-svg>
<u-svg src="ribbon:p3-06" fill="var(--coral)"></u-svg>
</span>
</div>
UNION.river(host, {
from:[-.08,.86], to:[1.08,.14], // go past 0/1 so the ends bleed off
count:18, size:.12, spread:.06, // size is a fraction of the HOST's width
dark:false, seed:7
});The register
Plain, warm, declarative. British English. The festival talks like someone who has run one before, not like a brand launching one.
No em dashes, anywhere, in copy or in code comments. Commas, semicolons, full stops and brackets do the work.
Never
No hype, no "curated", no "elevated", no "iconic", no empty intensifiers, nothing that reads as a press release. No exclamation marks on a practical instruction.
Care surfaces
Welfare, lost child, first aid and accessibility drop the brand voice entirely. Black on yellow, numbered steps, the phone number last and biggest. Plain sentences. The brand steps back because being found matters more than sounding like us.
The only placeholders. Nothing is invented: no artist, no price, no sponsor, no phone number, no URL.
ARTIST NAME HERE
HEADLINER HERE
to confirm
£ to confirm
The fixed facts. These are the only facts the system knows.
- UNIÓN, accent on the Ó, always
- Friday 9 July 2027
- Beckenham Place Park, London
- 11,500 capacity
- Main Stage, The Grove, The Corner
- Doors 2:30pm, curfew 10:30pm
- Rail: Beckenham Hill, Beckenham Junction
Anatomy, tokens, behaviour, a11y, markup
Why placeholders are visible
A placeholder is set in the same type as the real thing would be, at the same size, so a layout can be judged before the lineup exists. It is never greeked and never hidden.
When NOT to use
Never a plausible-looking fake. A made-up artist name in a mockup gets quoted back as real within a week.
The thirty source mockups, the source of taste for the whole system. Where the system and a mockup disagree, the mockup wins. Each sits at its own ratio and is never cropped. Note where the pattern runs all over: it is always the skin of a physical object.
Anatomy, tokens, behaviour, a11y, markup
Anatomy
A CSS-column masonry. Columns, not a grid, because a grid would force a row height and therefore a crop. Four columns at 1440, three at 1240, two at 820, one at 460.
The rule
A reference is NEVER cropped. Only our own photography may be cropped, and there is no photography in this pack.
Accessibility
Each image carries alt text naming what the mockup shows, and intrinsic width and height so the column does not reflow as images load.