Oxide house system
One ring. Two names.
Everything a customer sees from Oxagen and Stella comes from one system, Oxide: soot and bone grounds, the open ring, Space Grotesk at its regular weight, and a gold that marks who acts.
Oxide
The decision
Mac set Oxide on 2026-10-09. It takes its look from macanderson.com: a warm near-black ground called soot (#151515), a cream ground called bone (#FCFDFC), the open ring as the mark, Space Grotesk headings at weight 400, round pill controls, and a brighter gold with a new rust in place of the old metal gold on obsidian.
- Gold is identity, and the one action on a screen. It is the agent dot, stella's asterisk, the first eyebrow's square, the primary button, and the focus ring on soot. It never marks a state, never fills a surface, and never colours words.
- Rust is where you are, and depth. The current tab, the current page, a link's underline, a pressed filter, the ember glow, the radar's sweep, the focus ring on bone, and at most one accent word on a screen.
- Every status is a glyph and a word. Colour backs them up and is never the only signal.
- Panels at rest are solid. Glass is only for what floats: the sticky bar, menus, popovers, toasts, dialogs, drawers, and the command menu.
- The mark is the only picture we own. A surface that needs a picture builds it from the ring, the person, the agent, and the radar.
Logos
Wordmarks
Both words are Space Grotesk 400, lowercase, at 131.7px per em. oxagen's x takes the gold, gold-deep on bone, and every other letter takes the text colour. oxagen is 453 by 93 in its ink box. stella is followed by a drawn gold asterisk, 399 by 94 with it.
The drawn asterisk
stella's asterisk is drawn, not taken from the font. It has six arms from one centre, made of three strokes with round caps: one upright, and two at 30 degrees either side of horizontal. Its box is 0.42 of the font size, and its top meets the cap height. Its stroke changes with the size the word is set at, so it keeps the weight of the letters: 3.4 on a 24 box at 64px and up, 3.7 at 38px, 4.2 at 25.4px, and 4.7 at 20px.
Variants
Lockups
A lockup is the mark, a gap, and the word. The mark's 64 grid stands 1.5 times the font size, the gap is a third of the mark's height, and the mark centres on the x-height. stella's lockup drops the asterisk after the word, because the icon already holds it. oxagen's lockup carries the gold twice, in the agent dot and the x. stella's carries it once, in the asterisk.
Minimum sizes
Wordmark 88px wide on screen, 24mm in print. Icon 24px, 8mm. Lockup 120px, 32mm. Below that, use the favicon.
Misuse
Icons
The open ring
The oxagen mark is the open ring, drawn on a 64 grid. The ring is centred at (32, 32) with radius 21 and a 4.5 stroke with round caps. Its gap is 64 degrees wide, centred 45 degrees up and to the right. The person is a dot of radius 5.5 at the centre, in the text colour. The agent is a gold dot of radius 4.25 at the gap, on the ring itself. On soot a rust glow sits behind the agent. On bone the agent takes gold-deep and the glow drops.
The person sits at the centre. The ring is the agent's loop. The gap is where the person steps in.
stella shares the ring. Its gold asterisk sits at the centre in place of both dots, with arms of 9 and a 4.2 stroke. The gap stays open for the person.
Small marks thicken so the gap stays open. For oxagen the ring stroke, the person, and the agent are 32px: 5, 6, and 5, 24px: 5.5, 6.5, and 5.5, 16px: 7, 7.5, and 7. stella's ring and asterisk thicken the same way. A favicon PNG comes from a tile, because a ring in the text colour on nothing is no favicon on a tab of the same colour.
Colour
Palette
The gold is #F4B13E. Its two neighbours are derived from it in OKLCH, a colour space where equal steps look equal, and the build fails if they stop matching. The gold clears 9.7:1 on soot. On bone it is 1.8:1, so it never sets words there.
Rust is new in Oxide. Its words clear 4.5:1 on every surface of their theme, the same check the build runs for a status word.
Ember ramp
The ember ramp is for charts of heat and volume, from low to high. It runs from rust's darkest tone up through the gold. The tokens are --ox-ember-0 to --ox-ember-7.
Grounds
Dark comes first. Each step up from the canvas is one step lighter on soot: the sidebar and fields, the panel, then the lifted row a pointer or the current item takes. On bone the lifted row steps darker. Every page ships both themes.
Text
Text comes in four roles, each with one value on soot and one on bone. Every role that carries meaning clears 4.5:1 on its ground, and the build checks it. The quietest text is for decoration only.
| Role | On soot | On bone | Use |
|---|---|---|---|
| primary | Aa #FCFDFC 17.9:1 | Aa #151515 17.9:1 | headings and the words that matter most |
| body | Aa #C8C7C2 10.8:1 | Aa #3A3936 11.3:1 | paragraphs and table cells |
| secondary | Aa #9B9993 6.4:1 | Aa #52514E 7.8:1 | labels, captions, help text, and eyebrows |
| quietest | Aa #898782 5.1:1 | Aa #898782 3.5:1 | decoration only, never a word the reader needs |
Status
A status shows a 16px glyph and a word, never colour alone. A filled dot is allowed or settled. The orbit is live work. The open ring with the mark's gap waits on a person. A diamond is failed. A hollow ring is quiet. The word sits beside the glyph in primary text at weight 500.
The hues are verdigris for allowed, lapis for approval, garnet for failed, denied, and critical, and verdigris teal for proven. Quiet states, such as waiting or closed, take secondary text.
| On soot | On bone | Meaning |
|---|---|---|
| allowed mark #5FBF9F words #60C0A0 | allowed mark and words #19704F | a request a rule allowed |
| approval mark #7AA2F0 words #81AAF9 | approval mark and words #2E5FBF | a request routed to a person |
| denied mark #E5596E words #FF7688 | denied mark and words #B3283F | a request a rule denied |
| proven mark #4FB3A9 words #5CBEB4 | proven mark and words #1C6F68 | a result a named check proved |
| failed mark #E5596E words #FF7688 | failed mark and words #B3283F | a check that did not hold |
| critical mark #E5596E words #FF7688 | critical mark and words #B3283F | a run that needs a person now |
Priority
Priority is an outlined badge with no fill, in Geist Mono 600. P0 takes a garnet border and garnet words. An item with no priority reads "none" in secondary text, with no badge.
P0 P1 P2 P3 none
Every token
| Token | Value | On soot | On bone | Use | |
|---|---|---|---|---|---|
| --ox-gold | #F4B13E | 9.7:1 | 1.8:1 | the gold: identity, and the one action on a screen. flat, always | |
| --ox-gold-bright | #FFC567 | 11.7:1 | 1.5:1 | the gold action under the pointer on soot; the shimmer's light | |
| --ox-gold-deep | #DF9D21 | 7.8:1 | 2.3:1 | the gold as a mark on bone: the agent dot, the asterisk, the eyebrow square | |
| --ox-gold-text-ink | #896016 | 3.3:1 | 5.5:1 | deprecated: gold as words on bone. Words take rust-text-ink | |
| --ox-on-gold | #1A1208 | 1.0:1 | 18.2:1 | the ink on a gold fill, in both themes | |
| --ox-rust | #F4B13E | 9.7:1 | 1.8:1 | where you are, as a mark and the sweep on soot: a gold stop | |
| --ox-rust-text | #F4B13E | 9.7:1 | 1.8:1 | where you are, as words on soot: links and the accent word | |
| --ox-rust-ink | #9B6900 | 3.8:1 | 4.7:1 | where you are, as a mark and the focus ring on bone | |
| --ox-rust-text-ink | #865900 | 3.0:1 | 6.0:1 | where you are, as words on bone: links and the accent word | |
| --ox-rust-deep | #624000 | 2.0:1 | 9.1:1 | the deep gold, for a fill under light text | |
| --ox-ink | #151515 | 1.0:1 | 17.9:1 | the dark canvas: soot | |
| --ox-void | #111111 | 1.0:1 | 18.5:1 | one step below the canvas: the sidebar and fields | |
| --ox-panel | #1A1A19 | 1.0:1 | 17.1:1 | panels, cards, code blocks | |
| --ox-hl | #20201F | 1.1:1 | 16.0:1 | a lifted row: hover and current | |
| --ox-border | #2C2C2C | 1.3:1 | 13.7:1 | hairlines on soot | |
| --ox-rule | #474746 | 2.0:1 | 9.1:1 | a control's border on soot | |
| --ox-paper | #FCFDFC | 17.9:1 | 1.0:1 | the light canvas: bone | |
| --ox-paper-void | #FBFBF9 | 17.6:1 | 1.0:1 | one step from bone: the sidebar and fields | |
| --ox-paper-panel | #FFFFFF | 18.3:1 | 1.0:1 | light panel | |
| --ox-paper-hl | #EDECE8 | 15.4:1 | 1.2:1 | a lifted row on bone: hover and current | |
| --ox-paper-border | #E3E4E3 | 14.3:1 | 1.3:1 | hairlines on bone | |
| --ox-paper-rule | #CCCCCB | 11.4:1 | 1.6:1 | a control's border on bone | |
| --ox-text | #FCFDFC | 17.9:1 | 1.0:1 | primary text on ink | |
| --ox-text-body | #C8C7C2 | 10.8:1 | 1.7:1 | body text on ink | |
| --ox-muted | #9B9993 | 6.4:1 | 2.8:1 | secondary text on ink | |
| --ox-dim | #898782 | 5.1:1 | 3.5:1 | the quietest text on ink: decoration only | |
| --ox-text-ink | #151515 | 1.0:1 | 17.9:1 | primary text on paper | |
| --ox-text-ink-body | #3A3936 | 1.6:1 | 11.3:1 | body text on paper | |
| --ox-muted-ink | #52514E | 2.3:1 | 7.8:1 | secondary text on paper | |
| --ox-muted-text-ink | #52514E | 2.3:1 | 7.8:1 | secondary text on paper and on a lifted row there | |
| --ox-dim-ink | #898782 | 5.1:1 | 3.5:1 | the quietest text on paper: decoration only |
Type
Typefaces
Space Grotesk sets hero copy, every h1 to h3, and both wordmarks, on every surface, the app included. It always takes weight 400, and never runs below 20px. At the shipped bases its smallest step is 21.14px. Geist sets h4 to h6 and every other line. Geist Mono sets code, ids, digests, agent and runtime names, figures, and eyebrows.
Aeonik, Aeonik Mono, Aeonik Fono and Monaspace Neon load too, so a page that still names one keeps working. No role reads them.
Samples
Scales
A surface picks one scale and keeps it. The marketing scale (text-m-*) is large and spaced, for a page read once. The app scale (text-a-*) is dense, for the web app, panels, tables, and logs. Each scale has one base: 16px on marketing and 14.8px in the app. Every other step is the base times its ratio, so a new base moves every step.
The steps under the base are for labels, badges, timestamps, and table headers: app micro is 12.69px, app 2xs is 11.1px, and marketing micro is 14px. Running text, buttons, inputs, menu items, and table cells use the base. Every size reads a step token or a house utility.
| Step | Marketing face | Ratio | Size, leading, weight, tracking | App face | Ratio | Size, leading, weight, tracking |
|---|---|---|---|---|---|---|
| base | --ox-m-base | 16 | --ox-a-base | 14.8 | ||
| hero | none | Space Grotesk | 3.648649 | 54, 1, 400, -0.035em | ||
| h1 | Space Grotesk | 4.5 | 72, 1, 400, -0.035em | Space Grotesk | 2.567568 | 38, 1.05, 400, -0.03em |
| h2 | Space Grotesk | 2.5 | 40, 1.1, 400, -0.025em | Space Grotesk | 1.714286 | 25.37, 1.15, 400, -0.02em |
| h3 | Space Grotesk | 1.75 | 28, 1.2, 400, -0.02em | Space Grotesk | 1.428571 | 21.14, 1.25, 400, -0.015em |
| h4 | Geist | 1.25 | 20, 1.35, 600, -0.01em | Geist | 1.142857 | 16.91, 1.35, 600, -0.01em |
| body | Geist | 1 | 16, 1.625, 400, 0 | Geist | 1 | 14.8, 1.5, 400, 0 |
| micro | Geist | 0.875 | 14, 1.5, 400, 0 | Geist | 0.857143 | 12.69, 1.45, 400, 0 |
| 2xs | none | Geist | 0.75 | 11.1, 1.3, 500, 0 | ||
Weights
| Token | Weight | Sample | Use |
|---|---|---|---|
| --ox-weight-display | 400 | Runs waiting for approval | hero copy and every h1 to h3, in Space Grotesk |
| --ox-weight-logo | 400 | Runs waiting for approval | the two wordmarks |
| --ox-weight-heading | 400 | Runs waiting for approval | a section heading, h1 to h3, in Space Grotesk |
| --ox-weight-subheading | 600 | Runs waiting for approval | h4 to h6, in Geist |
| --ox-weight-ui | 500 | Runs waiting for approval | buttons, labels, navigation, and eyebrows |
| --ox-weight-body | 400 | Runs waiting for approval | paragraphs, table cells, and help text |
Tracking
Tracking is the space between letters. Headings track tighter as they grow, so large type does not look loose. The eyebrow opens up.
| Token | Value | Sample | Where |
|---|---|---|---|
| --ox-tracking-hero | -0.035em | See which agent spent what | marketing h1, app hero |
| --ox-tracking-display | -0.03em | See which agent spent what | app h1 |
| --ox-tracking-title | -0.025em | See which agent spent what | marketing h2 |
| --ox-tracking-headline | -0.02em | See which agent spent what | marketing h3, app h2 |
| --ox-tracking-subhead | -0.015em | See which agent spent what | app h3 |
| --ox-tracking-heading | -0.01em | See which agent spent what | marketing h4, app h4 |
| --ox-tracking-none | 0 | See which agent spent what | every other step, and the wordmarks |
| --ox-tracking-eyebrow | 0.08em | See which agent spent what | the eyebrow |
Type rules
- Headings use sentence case. Capitalise the first word and proper names only.
- An eyebrow is Geist Mono 500, uppercase, at 0.08em tracking, in secondary text. It is 2xs in the app and micro on marketing. The first eyebrow on a page carries a 7px gold square with a soft rust glow, which counts as the identity.
- A stat's figures take Geist Mono 400 at the h2 size. Every digit is the same width, so columns line up.
- One accent word per screen, at most. It stays upright, in rust words, in an empty state, onboarding, or a marketing hero.
Surfaces
Shape and surface
A panel at rest is solid: a card, a panel, a table's frame, or the sidebar. It takes 19px corners, a 1px hairline, a lit top edge, and a soft deep shadow. Glass is only for what floats over the page.
Corner radius
Each step multiplies one base, --ox-radius-base, which is 0.45rem. A card takes rounded-4xl. On the website, cards and panels round at 18px (--ox-radius).
Canvas
The canvas is the ground under every surface. It carries the ember glow, rust at 0% on soot and 0% on bone, as a radial about 820px wide at the top left, behind the mark. Film grain sits over the canvas, never over a panel. A product sets both as --glass-canvas.
Glass
A glass surface keeps part of its fill and blurs what lies beneath it, so the page shows through as soft colour. Text that rests sits on a solid panel.
| Level | Use | Fill on soot / bone | Blur on soot / bone | Saturation |
|---|---|---|---|---|
| panel | a card, a panel, the sidebar, and a table's frame at rest: solid | 100% / 100% | 0px / 0px | 100% |
| bar | a sticky bar over scrolling content: the site's nav, the app's header | 75% / 78% | 12px / 12px | 150% |
| popover | a menu, a select's list, a tooltip, a hover card, and a toast | 70% / 55% | 40px / 16px | 150% |
| sheet | a dialog, the command menu, a drawer, and the phone nav | 85% / 85% | 40px / 24px | 150% |
- Text on a sticky bar takes the primary text colour. It stays above 8:1 whatever scrolls under it.
- Every glass surface has an opaque fallback. A browser with no backdrop filter, and a reader who asks for reduced transparency or more contrast, get the surface opaque.
- A dialog sits on a scrim, the dark layer that covers the page behind it, with a 4px blur.
The agent runs deploy-prod once you approve.
Interaction
Hover and press
Buttons, chips, filters, and the search field are pills. A button is 44px tall, a small button 36px, and a chip 40px. Fills, borders, text, and underlines change over 200ms. The press and the gold action's glow take 250ms, and only the press moves on its curve. A pressed button sinks to 0.97. With reduced motion on, hover and press apply at once. Try each control below.
| Work | Status | Agent |
|---|---|---|
| Rotate the staging keys | Running | night-shift |
| Review the March invoices | In review | ledger |
| Retry the failed deploy | Failing | deploy-bot |
Motion
Spinners and the radar
The oxagen spinner is the mark thinking: the agent dot and a rust sweep orbit the centre once every 2.4 seconds. Below 32px the sweep drops and the strokes thicken. stella's asterisk turns once every 3.2 seconds inside its still ring. The radar is the full orbit field, for a page or panel that waits. All of it is CSS inside the SVG, and it holds still at rest under reduced motion.
Art
Composition
A piece of art is a ground with the ember glow and the grain, the brand's mark or its radar placed off-centre, and at most a few lines of type. Headlines are Space Grotesk 400, and every other line is Geist. Every surface below is the vector the kit ships.
Wallpapers
radar is the default: a still of the orbit field at the top right of the Work page, turned 65 degrees so the agent dot and the sweep are in view. graph wires a field of nodes to the mark in rust. blocks rebuilds the mark from tiles. orbit hangs rings of nodes off the mark. glow and quiet are the mark alone, faint and as a hairline. word and echo carry the wordmark. eclipse draws a giant open ring with a rust glow at its gap. trails circles the mark with star trails that all turn the same 56 degrees. contour draws a contour map with the mark on its summit. dusk sets the mark on a horizon, with its reflection in dashes on the water below. emboss presses the mark into the ground and stamps its gold in foil.
Social
Ads
Every ad takes its copy from an approved, launch-released entry in messages/. The kicker is the ad's eyebrow, with its gold square. The rule under the headline is rust. The workforce ad takes the radar behind its type, because it draws many agents under one control plane.
Content cards
Files
Kit files
No file in this kit is drawn by hand. Every PNG is a render of the SVG beside it, and every SVG comes from build/. Every value comes from theme/theme.json, so one change there moves every asset on the next run.
python3 -m venv .venv && .venv/bin/pip install fonttools brotli pyyaml
brew install harfbuzz librsvg
.venv/bin/python build/build.py --check # check the theme and every file, write nothing
.venv/bin/python build/build.py # every asset
.venv/bin/python build/build.py --svg # skip the raster pass
.venv/bin/python build/playbook.py # rebuild this document