Lemma
uselemma.aiZero radius everywhere. The page is built from one-pixel rules instead of cards, and diagrams live in numbered plates labelled LEM. 1.1 like figures in a proof.
Five design systems reverse-engineered from live CSS, each written up as a guideline an agent can build from, and each one built out below as working pages. Only Lemma publishes its own brand page. Everything else was read off the DOM.
Zero radius everywhere. The page is built from one-pixel rules instead of cards, and diagrams live in numbered plates labelled LEM. 1.1 like figures in a proof.
One type size does almost everything. Every visible run but four is 14px/20px, so hierarchy comes from colour, not size, and one gold word carries the whole editorial hand.
The data is the interface. 12px default, the scale tops out at 20px and is used once, every numeral is mono, and one 120ms animation is the entire motion budget.
Identity from colour, shape and softness rather than a typeface. Squircle corner smoothing, translucency as material, one cranberry accent spent carefully.
Near-black surfaces with translucent white layers instead of fills. The 9:16 rounded player, a 48px circle action rail, a 240px guide. Dark only.
Zero radius everywhere. The page is built from one-pixel rules instead of cards, and diagrams live in numbered plates labelled LEM. 1.1 like figures in a proof.
Act as an excellent Lemma designer, information architect, and design engineer. The standard is that a stranger could not tell your page from a page Lemma's own team shipped. That is achieved through three things and almost nothing else: a strict typographic hierarchy in Britti Sans, a page built out of one-pixel rules instead of cards, and a single indigo accent spent only where it carries meaning.
Lemma is a reliability platform for AI agents. It catches regressions, spots failures, and improves agents in production before users do. The name comes from mathematics: a lemma is a small, proven proposition used as a building block for a larger proof. That idea is the whole design system. Every visual decision is doing the same job the name does, which is presenting evidence as something proven rather than something asserted.
Everything below traces to one of three sources, all official Lemma surfaces, all read on September 11, 2026:
uselemma.ai/brand, Lemma's own published brand page, with a live color palette, typeface specimens, and a type scale.uselemma.ai/brand/lemma-brand-guidelines.pdf, the official 15-page brand guidelines deck, version marked LEMMA 26'.uselemma.ai/, uselemma.ai/blog/a-taxonomy-of-agent-failures, uselemma.ai/careers, and uselemma.ai/changelog, plus the production JavaScript bundles, for everything the published guidelines do not cover: component recipes, hover states, prose theming, layout rhythm, motion, and the visualization color assignments.Rules for using this file:
*-brand-kit sites, or Figma Community files. Lemma publishes its own kit at uselemma.ai/brand, so there is no reason to guess.docs.uselemma.ai is Mintlify, a third-party docs platform. It is a Lemma surface but it is not Lemma-authored design. Do not extract patterns from it.Lemma's brand page states the intent directly: the palette is "built on a single primary hue, a deep and precise indigo that communicates reliability and technical rigor," the neutrals "are never decorative, they serve hierarchy," and the type system is "strict: mixing sizes outside their intended roles breaks the system's legibility."
The distinctive material choices, each one actionable:
border-radius: 0px. Buttons carry an explicit rounded-none. The only rounded corners on the entire site belong to simulated third-party UI (a mock Slack message, a mock macOS window) where the roundness is quoting someone else's product. Square corners are the single fastest tell of belonging.border-t in #D4D4DD. The main container carries xl:border-l xl:border-r, so the content column reads as a ruled measure running down the page. Absolutely positioned w-px bg-border elements act as structural verticals. There is not a single box-shadow anywhere in Lemma-authored CSS.#755CFE outlined rectangles with a small caption bar fused to the top edge, labeled in mono as LEM. 1.1, LEM. 2.3. The contents are plotter-style wireframes, point clouds, harmonographs, gravitational-lens fields, drawn in with stroke-dashoffset at 0.6 to 0.7 opacity. This is the figure-numbering convention of a mathematical paper, applied to a marketing page. It is the brand's most recognizable move.TRC-02F, LEM. 1.1), and status words. Never on prose, never on headings.#755CFE Lady Blue marks the healthy or active path. #EE5557 marks a regression or failure. Nothing is colored because it is important.#6E6885 via CSS mask-image, never rendered in their own brand colors.color-scheme: light, no dark tokens in Lemma's own layer, no theme switcher. The only dark-mode CSS on the site belongs to the cookie-consent and toast libraries.Most artifacts built from this file will be Zhiyuan's own projects, hackathon submissions, or integrations, not official Lemma surfaces. Keep the line visible.
You may borrow: the palette, the type scale, the square-corner rule, the hairline architecture, the mono-label idiom, the plate figure convention as a visual technique, the button recipes, the motion budget, and the starter stylesheet below.
You may not: use the Lemma symbol or logotype as a project mark, reproduce the isometric stacked-plane logo, put LEM. x.y plate numbers on work that is not Lemma's, imply Lemma authorship or endorsement, or replicate the platform.uselemma.ai app shell. If the artifact is an integration or a project built on Lemma, say so in words. A Lemma logo used as an attribution credit inside a clearly-third-party page is fine. A Lemma logo in a header position is not.
When requirements compete, protect them in this order:
--font-britti-sans: "brittiSans", "brittiSans Fallback", Arial, sans-serif;
--font-ibm-plex-mono: "IBM Plex Mono", "IBM Plex Mono Fallback", ui-monospace, monospace;
Britti Sans is a licensed typeface. Lemma's own brand page says plainly: "Typefaces are licensed and not available for download." Lemma self-hosts three OTF weights (400, 500, 600) from its own origin. You cannot lift those files. For anything that is not an official Lemma surface, license Britti Sans from its foundry or substitute a geometric-humanist grotesk with a similar single-storey feel and slightly narrow set. Lemma's own fallback declaration is local("Arial"), which tells you they accepted a plain fallback rather than a webfont substitute, so do not reach for a Google Fonts lookalike and call it Britti Sans.
IBM Plex Mono is open source under the OFL and available from Google Fonts. Use the real thing. Weights loaded: 300, 400, 500.
The type scale, from Lemma's published guidelines, verified against live rendering:
| Role | Size | Weight | Line height | Tracking | Face |
|---|---|---|---|---|---|
| Display | 64px | 500 Medium | 100% | -1% | Britti Sans |
| H1 | 48px | 500 Medium | 100% | -1% | Britti Sans |
| H2 | 32px | 400 Regular | 105% | -1% | Britti Sans |
| H3 | 24px | 400 Regular | 105% | -1% | Britti Sans |
| Body L | 18px | 400 Regular | 130% | see below | Britti Sans |
| Body M | 16px | 400 Regular | 130% | see below | Britti Sans |
| Body S | 14px | 400 Regular | 130% | see below | Britti Sans |
| Label | 12px | 400 Regular | 125% | +0.10em to +0.15em, uppercase | IBM Plex Mono |
DISCREPANCY 1, heading weight. The published guidelines assign Medium (500) to Display and H1. Every production heading on
uselemma.airenders at Regular (400) withfont-normalwritten explicitly into the class. Both are official Lemma. Prescription: use 400 for anything that sits in a page alongside body copy, because that is what every shipped Lemma page does and mixing the two reads as inconsistent. Reserve 500 for isolated large-format display use such as a cover slide or a poster, which is the context the PDF specimen is showing.
DISCREPANCY 2, letter spacing on body. The guidelines specify -1% for body as well as headlines. Production sets a global
letter-spacing: 0.32pxon<body>, which is +0.02em at 16px, the opposite sign. Headings do carry real negative tracking (-0.01emand-0.02em), matching the spec. Prescription: follow production. Negative tracking at 15px to 18px in Britti Sans closes the counters and hurts legibility, which is the likely reason they changed it.
Responsive heading behavior as shipped:
33px at mobile, 40px from lg, 48px from 3xl, line-height: 1.1.30px, 36px from md, 44px from 3xl, line-height: 1.2.30px, 48px from md, 60px from lg, line-height: 1.1, tracking -0.02em.30px / H3 24px, both line-height: 1.15, tracking -0.01em.Mono label tracking is not one value. Lemma uses a deliberate ladder and picks by density: 0.06em, 0.08em, 0.1em, 0.12em, 0.14em, 0.15em. Tighter for inline machine IDs, wider for standalone section eyebrows. A 10px eyebrow at 0.15em and a 12px logo-row label at 0.14em are both shipped. Never set a mono label below 0.06em and never above 0.15em.
Rules that hold everywhere:
max-w-[72ch], which renders at about 721px at 18px.Lemma publishes a named palette. Use the names, they tell you intent.
Core palette (from uselemma.ai/brand):
| Name | Hex | RGB | Role |
|---|---|---|---|
| whitething | #F2F2F2 | 242, 242, 242 | Page background, everywhere |
| Light Gray | #D5D5D5 | 213, 213, 213 | Accent light, dividers in print |
| Teal | #58E4B6 | 88, 228, 182 | Positive state, sparingly |
| Lady Blue | #755CFE | 117, 92, 254 | Primary brand anchor |
| bluepurplething | #322C8A | 50, 44, 138 | Deep indigo, gradient stop |
| Admiral | #1D1956 | 29, 25, 86 | All text, primary button fill |
Lemma's own words: "Primary color: #755CFE. This is the anchor of the brand, it should appear in every application of the identity."
Palette note. The brand PDF names
#F4F4F4"Wild Sans" where the live brand page names#F2F2F2"whitething." The production<html>background computes to#F2F2F2. Use#F2F2F2.
Neutral extensions (Lemma: "never decorative, they serve hierarchy"):
| Name | Hex |
|---|---|
| Gray Hint | #E6E6E6 |
| New Gray | #A4A4A4 |
| Power Gray | #717171 |
| Carbon Fiber | #262626 |
| Shocking Black | #111111 |
The tinted grays are the ones you will actually use. Production text is never neutral gray, it is always an indigo-tinted gray derived from Admiral. This is the single most-missed detail in the system.
| Token | Hex | Use |
|---|---|---|
--color-gray-tint-300 | #D3D3E5 | Faintest tinted line |
--color-gray-tint-400 | #A1A0B8 | Mono metadata, inactive IDs |
--color-gray-tint-500 | #706E8B | Tertiary text |
--color-gray-tint-600 | #52506F | Secondary text, dense |
--color-muted-foreground / --color-gray | #6C6991 | Nav links, captions, eyebrows |
| logo-row gray | #6E6885 | Masked customer logos only |
Semantic roles as shipped:
| Token | Value | Notes |
|---|---|---|
--color-background | #F2F2F2 | |
--color-card / --color-popover | #FFFFFF | White is a surface, not the page |
--color-foreground / --color-primary | #1D1956 | |
--color-primary-foreground | #F2F2F2 | Not white |
--color-brand-purple | #755CFE | |
--color-brand-purple-deep | #5D4EE7 | Links, shine base |
--color-brand-navy-light | #322C8A | |
--color-line / --color-border | #D4D4DD | The hairline. Highest-frequency border on the site |
--color-hairline | #E6E6EF | Softer rule |
--color-hairline-soft | #F3F3FC | Softest rule |
--color-secondary / --color-muted | #E6E6E6 | |
| neutral surface | #E8E8E8 | Secondary button, list rows |
| neutral surface hover | #E0E0E0 | The paired hover. Always this pair |
--color-input | #E5E5EE | |
--color-ring | #755CFE | Focus ring |
--color-accent-light | #D5D5D5 |
The three rules that actually govern color:
text-primary/65, which computes to oklab(0.262919 0.0168294 -0.104771 / 0.65). That is #1D1956 at 65%. Use color-mix(in oklab, #1D1956 65%, transparent) or rgb(29 25 86 / 0.65). Setting body copy to #6C6991 instead looks close and reads wrong, because the opacity version picks up the #F2F2F2 page underneath.border: 1px solid <color>/40 plus background: <color>/8 plus full-strength text. Applied to #EE5557 in production as border-[#EE5557]/40 bg-[#EE5557]/[0.08]. A left-rule variant uses border-l-2 at full strength with bg at 5%.let l = "#755CFE", s = "#EE5557", u = ["#322C8A", "#1D1956", "#5D4EE7"], which is primary, regression, and a depth ramp. Healthy lines are #2BC392, regression lines are #EE5557. Do not turn a heading, a price, or a recommendation purple because it matters.Data and state colors (from production bundles, not the published palette, so treat as observed rather than sanctioned):
| Hex | Role |
|---|---|
#EE5557 | Regression, failure, issue. The one you will need |
#2BC392 | Healthy series line |
#58E4B6 | Teal, positive, from the official palette |
#5D4EE7 | Deep purple, links and depth ramp |
#322C8A | Depth ramp |
#A6A9B3 | Neutral chart gray |
Colors such as #34C759, #FF3B30, #3C82F7, #1D1C1D, #1264A3 appear in the bundle but belong to simulated iOS and Slack chrome. They are not Lemma colors. Do not lift them.
Gradients. Lemma uses exactly two, both scoped:
/* Primary button hover overlay only */
linear-gradient(135deg, #322C8A 0%, #5D4EE7 50%, #1D1956 100%);
/* The logo mark itself, in the SVG. Do not reuse. */
linear-gradient(#8470FF, #2D2870);
There are no decorative gradients, no glows, no meshes, no grain. A gradient on a Lemma page is either a button hover or a mask used to fade a marquee edge.
Radius is zero. This is the rule with no exceptions.
border-radius: 0;
Every Lemma-authored button, input, chip, card, plate, list row, and callout computes 0px. Buttons carry rounded-none explicitly, which means it was a decision, not an oversight. The Tailwind radius scale is present in the stylesheet (--radius-sm through --radius-2xl) but Lemma-authored markup never reaches for it.
The only rounded corners you will find are inside deliberate simulations of other products: a rounded-[4px] Slack button, a macOS traffic-light window. If your artifact is quoting another product's UI, round it the way that product rounds it. Otherwise, zero.
There is no corner smoothing, no squircle, no corner-shape. Circles exist only as data points in SVG.
There are no shadows. The extractor found zero box-shadow declarations in Lemma-authored CSS. Every shadow token on the site belongs to the cookie-consent library.
Depth comes from four techniques instead, in order of how often Lemma uses them:
1px solid #D4D4DD is the workhorse. #E6E6EF and #F3F3FC when a rule needs to recede.#F2F2F2 page, #E8E8E8 resting interactive surface, #FFFFFF card. Three steps, all flat.outline: 1px solid rgb(0 0 0 / 0.1) with outline-offset: -1px, which keeps the image at its exact box while still having an edge.0.5px solid #755CFE on plate frames, which on a retina display renders as a genuinely finer line than 1px and is part of why the plates read as drafting rather than UI.If you feel the need for a shadow, you have a hierarchy problem. Fix it with a rule or a surface step.
Breakpoints, as configured:
| Name | Min width |
|---|---|
sm | 640px / 40rem |
md | 768px / 48rem |
lg | 1024px / 64rem |
xl | 1280px / 80rem |
2xl | 1536px / 96rem |
3xl | 1920px / 120rem (custom, Lemma-added) |
The 3xl breakpoint is real and used for genuine large-display scaling: H1 goes 40 to 48, H2 goes 36 to 44, button text goes 11 to 13. Build it in.
Container widths as shipped:
max-w-7xl (1280px), widening to a custom max-w-8xl at 3xl.xl:border-l xl:border-r border-line, so it is a visibly ruled measure, not an invisible container.max-w-[1040px] px-6.max-w-2xl px-4 sm:px-6.max-w-[1480px].max-w-[72ch].Section separation is border-t border-line plus vertical padding, never a card and never a background change. A typical section is relative z-[1] border-t border-line bg-background with pb-20 md:pb-28.
Spacing is Tailwind's 0.25rem base. Lemma adds one custom token, --spacing-subhead: 0.5rem, exposed as mb-subhead and mt-subhead, used exclusively for the gap between a section heading and its supporting paragraph. That is the only bespoke spacing value in the system, which tells you how much they cared about that one relationship.
Long-form vertical rhythm, from the prose config: h2 gets margin-top: 6rem and margin-bottom: 1.5rem; h3 gets 3rem and 1rem. Those are large and deliberate. Do not compress them.
/* Durations, by frequency of use */
--duration-fast: 150ms; /* all interactive state changes */
--duration-medium: 300ms; /* overlays, opacity reveals */
--duration-slow: 400ms; /* entrance transitions */
/* Easing */
--ease-out: cubic-bezier(0, 0, 0.2, 1); /* the default */
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-draw: cubic-bezier(0.33, 1, 0.68, 1); /* the shine sweep */
150ms ease-out is the house transition and covers essentially every hover and focus change. cubic-bezier(0.34, 1.56, 0.64, 1), an overshoot curve, appears twice on the entire site. Treat it as effectively unused.
What moves:
active:scale-[0.96] on every button, transitioning color, background-color, box-shadow, scale at 150ms ease-out. This is small and it is the detail that makes the UI feel like theirs..lemma-shine puts a 250%-wide linear-gradient(100deg, ...) behind text with background-clip: text, then animates background-position over 0.9s cubic-bezier(0.33, 1, 0.68, 1) on hover of .lemma-shine-host. Base color #5D4EE7, glint #C9C2FF. One sweep, once, on hover.stroke-dashoffset: 100 to 0, settling at opacity: 0.6 to 0.7. Named keyframes in production: lemma-gravlens-draw, lemma-harmograph-draw, lemma-sphere-draw, lemma-cube-draw, lemma-cube-flip, lemma-fade-in, lemma-blog-fade-up, lemma-reel, lemma-scroll-pulse.sticky top-0 h-screen and advances state as you scroll.What does not move: nothing bounces, nothing parallaxes, no section reveals on scroll as a decoration, no auto-playing marquee carries meaning, no typing cursors.
Every single animated component on the site ships a @media (prefers-reduced-motion: reduce) block that sets animation: none !important. This is not optional garnish in this system. If you add a Lemma-style animation, you add the reduced-motion block with it.
One base recipe, two variants, no third.
Base: inline-flex items-center justify-center gap-2
font-mono text-[11px] 3xl:text-[13px] uppercase font-normal
px-4 py-2 rounded-none whitespace-nowrap
transition-[color,background-color,box-shadow,scale] duration-150 ease-out
active:scale-[0.96]
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#755CFE] focus-visible:ring-offset-2
disabled:pointer-events-none disabled:opacity-50
[&_svg]:size-4 [&_svg]:shrink-0 [&_svg]:pointer-events-none
| Variant | Background | Text | Hover |
|---|---|---|---|
| Primary | #1D1956 | #FFFFFF | linear-gradient(135deg, #322C8A, #5D4EE7 50%, #1D1956) overlay fading opacity: 0 to 1 over 300ms ease-out |
| Secondary | #E8E8E8 | #1D1956 | #E0E0E0 |
The primary hover is implemented as a group with overflow-hidden and an absolute inset-0 gradient span at opacity-0 group-hover:opacity-100, with the label in a sibling relative z-10 span. Reproduce it that way rather than transitioning background-image, which does not interpolate.
Compact size, used in mobile nav: px-3 py-2 text-[10px]. Generous size, used in page CTAs: px-5 py-2.5 text-[11px].
Real labels from the site: BOOK A DEMO, LOGIN, READ THE DOCS, ALL POSTS. Always uppercase, always mono, always short.
Lemma's answer to a card grid. Used for job listings and index pages.
flex items-center gap-4 md:gap-8
bg-[#E8E8E8] p-3 pr-5
transition-colors hover:bg-[#E0E0E0]
Square corners, no border, no shadow, primary label at 16px Britti Sans, secondary metadata right-aligned in #6C6991. Note it reuses the exact same #E8E8E8 / #E0E0E0 pair as the secondary button. That pairing is a system, use it for any neutral interactive surface.
The signature component. Structure as shipped:
<figure class="flex flex-col">
<!-- caption bar, fused to the top of the frame -->
<div class="flex shrink-0 items-center px-2.5 py-1.5 border border-[#755CFE]">
<span class="font-mono text-[10px] text-[#755CFE]">LEM. 1.1</span>
</div>
<!-- body, borders on three sides so the caption bar's bottom edge is shared -->
<div style="border-left:0.5px solid #755CFE;
border-right:0.5px solid #755CFE;
border-bottom:0.5px solid #755CFE;">
<svg><!-- plotter-style wireframe, stroke #755CFE, opacity 0.6-0.7 --></svg>
</div>
</figure>
A minimal variant drops the label and uses a bare height: 6px top strip with all four borders, which reads as a tab.
Rules for plates:
#755CFE at 0.5px to 1px. Fill is transparent or the page background. Never a solid purple plate.#755CFE, and it is either a plate number or a very short caption. Never a sentence.xl:absolute xl:left-[2%] xl:top-[49.2%] percentage placement inside an aspect-[940/465] frame, collapsing to a lg:grid-cols-3 stack below xl./* inline chip */
border border-[#EE5557]/40 bg-[#EE5557]/[0.08]
px-2 py-0.5 font-mono text-[10px] uppercase text-[#EE5557]
/* callout */
border-l-2 border-[#EE5557] bg-[#EE5557]/[0.05] px-3 py-2.5
Swap #EE5557 for #755CFE or #2BC392 for other states. The 40 / 8 / 100 opacity ratio holds.
The failure-taxonomy field: a staggered grid of square-cornered blocks, each 1px solid outlined, carrying a mono ID (TRC-02F) in #A1A0B8. Neutral blocks are outlined only. Failure blocks fill #EE5557 with white text, an uppercase mono category line, and the offending output in quotes. The visual argument is that failures are a small colored minority inside a large neutral field, which is exactly the product's claim.
Complete theme, verbatim from the production Tailwind Typography config:
| Element | Treatment |
|---|---|
| Measure | max-w-[72ch], centered |
| Body | 18px, line-height: 1.6, #1D1956 |
| Lede | 24px, line-height: 1.375, #1D1956 |
| Headings | line-height: 1.15, tracking: -0.01em, text-balance, #1D1956 |
h2 | margin-top: 6rem, margin-bottom: 1.5rem |
h3 | margin-top: 3rem, margin-bottom: 1rem |
| Links | #5D4EE7, weight 400, underline, underline-offset: 0.15em, decoration at 40% opacity, going to full opacity on hover |
| Bold | #1D1956, same as body |
| Inline code | background: #E6E6E6 at 70%, px-1.5 py-0.5, 0.9em, weight 400, backtick pseudo-elements removed |
| Blockquote | border-left: 2px solid #755CFE, background: #FFFFFF, px-5 py-4, not italic, weight 400, #1D1956, smart quote pseudo-elements removed |
| Bullets and counters | #6C6991 |
| Captions | #6C6991 |
hr, th, td borders | #D4D4DD |
| Images | outline in rgb(0 0 0 / 0.1), outline-offset: -1px, no radius |
| Paragraphs and list items | text-pretty |
Note the two removals. Lemma strips Tailwind Typography's decorative backtick marks around inline code and its curly-quote marks around blockquotes. Both are default ornaments and both were deleted. That is the taste of this system in miniature.
Article eyebrow: mono, 10px, tracking: 0.15em, uppercase, #6C6991, margin-bottom: 1rem. Back link: mono, 10px, tracking: 0.1em, uppercase, #6C6991, hovering to #755CFE.
Header: logo left as symbol plus wordmark, center nav in Britti Sans at 14px #6C6991 hovering to #1D1956, right pair of LOGIN secondary and BOOK A DEMO primary. No border under it, no blur, no shadow.
Announcement bar: full-bleed #755CFE, white text, centered, with a dismiss X. This is the only place Lady Blue is used as a large background field on the marketing site, and it earns it by being temporary and dismissible.
Footer: product name and one-line description left, then RESOURCES and SOCIAL columns under uppercase 12px headings, copyright line, contact email. Section headings in the footer are the one uppercase Britti Sans exception on the site.
Sentence case for headings and prose. Uppercase for mono labels and buttons. Never title case.
Real headlines, which show the register better than any description: "Stop guessing why your agents fail." "Agents in production fail in unpredictable ways." "Surface failures you didn't know to look for." "Get notified on what matters." "Fix it where you work." "Ship with confidence." "Trust is non-negotiable." "Every release, documented." "Help us build the future of continual learning."
The pattern: short declarative sentence, ends in a period, names a concrete failure or a concrete action. Second person is used freely. Contractions are used freely. No hype adjectives, no "revolutionary," no "powerful."
Hard rules:
ISSUES, LIVE ALERTS, AGENTS, COMING SOON, TRUSTED BY TEAMS SHIPPING AGENTS.LEM. <section>.<index> for figures, TRC-<3 hex> for traces. Keep the format if you use them, change the prefix if the work is not Lemma's.Drop-in tokens using Lemma's own variable names, so future live inspection of uselemma.ai maps onto this file one-to-one.
/* Lemma design tokens
Extracted from uselemma.ai on 2026-09-11.
Sources: uselemma.ai/brand, lemma-brand-guidelines.pdf, live production CSS.
Light only. Lemma ships no dark theme. */
:root {
/* Type */
--font-britti-sans: "brittiSans", Arial, sans-serif; /* licensed, self-host only */
--font-ibm-plex-mono: "IBM Plex Mono", ui-monospace, monospace;
--type-display: 64px;
--type-h1: 48px;
--type-h2: 32px;
--type-h3: 24px;
--type-body-l: 18px;
--type-body-m: 16px;
--type-body-s: 14px;
--type-label: 12px;
--leading-heading: 1.1;
--leading-h2: 1.2;
--leading-prose: 1.6;
--leading-body: 1.3;
--tracking-heading: -0.01em;
--tracking-display: -0.02em;
--tracking-body: 0.02em; /* production, not the PDF spec. See DISCREPANCY 2 */
--tracking-label: 0.1em; /* ladder: 0.06 0.08 0.1 0.12 0.14 0.15 */
--weight-regular: 400;
--weight-medium: 500; /* large-format display only */
--weight-semibold: 600; /* exists in the family, unused on web */
/* Core palette */
--lemma-whitething: #f2f2f2;
--lemma-light-gray: #d5d5d5;
--lemma-teal: #58e4b6;
--lemma-lady-blue: #755cfe;
--lemma-bluepurple: #322c8a;
--lemma-admiral: #1d1956;
/* Neutral extensions */
--lemma-gray-hint: #e6e6e6;
--lemma-new-gray: #a4a4a4;
--lemma-power-gray: #717171;
--lemma-carbon-fiber: #262626;
--lemma-shocking-black: #111111;
/* Semantic, as named in production */
--color-background: #f2f2f2;
--color-foreground: #1d1956;
--color-primary: #1d1956;
--color-primary-foreground:#f2f2f2;
--color-card: #ffffff;
--color-popover: #ffffff;
--color-secondary: #e6e6e6;
--color-muted: #e6e6e6;
--color-muted-foreground: #6c6991;
--color-brand-purple: #755cfe;
--color-brand-purple-deep: #5d4ee7;
--color-brand-navy-light: #322c8a;
--color-line: #d4d4dd;
--color-border: #d4d4dd;
--color-hairline: #e6e6ef;
--color-hairline-soft: #f3f3fc;
--color-input: #e5e5ee;
--color-ring: #755cfe;
--color-accent-light: #d5d5d5;
/* Indigo-tinted grays. Use these, not neutral grays */
--color-gray-tint-300: #d3d3e5;
--color-gray-tint-400: #a1a0b8;
--color-gray-tint-500: #706e8b;
--color-gray-tint-600: #52506f;
/* Interactive neutral surface pair */
--surface-neutral: #e8e8e8;
--surface-neutral-hover: #e0e0e0;
/* State */
--state-regression: #ee5557;
--state-healthy: #2bc392;
--state-positive: #58e4b6;
--chart-neutral: #a6a9b3;
/* Shape. There is exactly one value */
--radius: 0;
/* Motion */
--duration-fast: 150ms;
--duration-medium: 300ms;
--duration-slow: 400ms;
--ease-out: cubic-bezier(0, 0, 0.2, 1);
--ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
--ease-draw: cubic-bezier(0.33, 1, 0.68, 1);
/* Layout */
--container-content: 80rem; /* max-w-7xl */
--container-reading: 1040px;
--container-prose: 72ch;
--spacing-subhead: 0.5rem;
--breakpoint-3xl: 120rem;
}
html { background: var(--color-background); color-scheme: light; }
body {
font-family: var(--font-britti-sans);
font-size: var(--type-body-m);
font-weight: var(--weight-regular);
line-height: 1.5;
letter-spacing: var(--tracking-body);
color: var(--color-foreground);
background: var(--color-background);
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 {
font-weight: var(--weight-regular);
letter-spacing: var(--tracking-heading);
line-height: var(--leading-heading);
color: var(--color-foreground);
text-wrap: balance;
}
h1 { font-size: clamp(33px, 4vw, var(--type-h1)); }
h2 { font-size: clamp(30px, 3vw, var(--type-h2)); line-height: var(--leading-h2); }
h3 { font-size: var(--type-h3); line-height: 1.15; }
.lemma-secondary-text { color: rgb(29 25 86 / 0.65); } /* never a flat gray */
.lemma-label {
font-family: var(--font-ibm-plex-mono);
font-size: 10px;
letter-spacing: var(--tracking-label);
text-transform: uppercase;
color: var(--color-muted-foreground);
}
.lemma-section { border-top: 1px solid var(--color-line); padding-block: 5rem 7rem; }
.lemma-measure {
max-width: var(--container-content);
margin-inline: auto;
}
@media (min-width: 80rem) {
.lemma-measure {
border-left: 1px solid var(--color-line);
border-right: 1px solid var(--color-line);
}
}
/* Buttons */
.lemma-btn {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
border: 0;
border-radius: var(--radius);
font-family: var(--font-ibm-plex-mono);
font-size: 11px;
font-weight: var(--weight-regular);
text-transform: uppercase;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
transition: color var(--duration-fast) var(--ease-out),
background-color var(--duration-fast) var(--ease-out),
scale var(--duration-fast) var(--ease-out);
}
.lemma-btn:active { scale: 0.96; }
.lemma-btn:focus-visible {
outline: 2px solid var(--color-ring);
outline-offset: 2px;
}
@media (min-width: 120rem) { .lemma-btn { font-size: 13px; } }
.lemma-btn--primary { background: var(--color-primary); color: #fff; }
.lemma-btn--primary::before {
content: "";
position: absolute;
inset: 0;
opacity: 0;
background: linear-gradient(135deg, #322c8a 0%, #5d4ee7 50%, #1d1956 100%);
transition: opacity var(--duration-medium) var(--ease-out);
}
.lemma-btn--primary:hover::before { opacity: 1; }
.lemma-btn--primary > * { position: relative; z-index: 10; }
.lemma-btn--secondary { background: var(--surface-neutral); color: var(--color-primary); }
.lemma-btn--secondary:hover { background: var(--surface-neutral-hover); }
/* Interactive list row */
.lemma-row {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1.25rem 0.75rem 0.75rem;
background: var(--surface-neutral);
border-radius: var(--radius);
transition: background-color var(--duration-fast) var(--ease-out);
}
.lemma-row:hover { background: var(--surface-neutral-hover); }
/* State chip */
.lemma-chip {
display: inline-flex;
align-items: center;
gap: 0.25rem;
padding: 0.125rem 0.5rem;
border: 1px solid rgb(238 85 87 / 0.4);
background: rgb(238 85 87 / 0.08);
color: var(--state-regression);
font-family: var(--font-ibm-plex-mono);
font-size: 10px;
text-transform: uppercase;
border-radius: var(--radius);
}
/* Plate figure */
.lemma-plate { display: flex; flex-direction: column; }
.lemma-plate__caption {
display: flex;
align-items: center;
padding: 0.375rem 0.625rem;
border: 1px solid var(--color-brand-purple);
font-family: var(--font-ibm-plex-mono);
font-size: 10px;
color: var(--color-brand-purple);
}
.lemma-plate__body {
border-left: 0.5px solid var(--color-brand-purple);
border-right: 0.5px solid var(--color-brand-purple);
border-bottom: 0.5px solid var(--color-brand-purple);
}
.lemma-plate svg { stroke: var(--color-brand-purple); fill: none; opacity: 0.7; }
/* Shine sweep */
.lemma-shine {
--shine-base: var(--color-brand-purple-deep);
--shine-glint: #c9c2ff;
color: var(--shine-base);
background-image: linear-gradient(100deg,
var(--shine-base) 0%, var(--shine-base) 46%,
var(--shine-glint) 50%,
var(--shine-base) 54%, var(--shine-base) 100%);
background-size: 250% 100%;
background-position: 100% 0;
background-repeat: no-repeat;
background-clip: text;
-webkit-text-fill-color: transparent;
}
@keyframes lemma-shine { to { background-position: -150% 0; } }
.lemma-shine-host:hover .lemma-shine {
animation: lemma-shine 0.9s var(--ease-draw) 1;
}
@media (prefers-reduced-motion: reduce) {
.lemma-shine-host:hover .lemma-shine,
.lemma-plate svg { animation: none !important; }
.lemma-btn { transition: none; }
}
Each of these breaks belonging on sight.
rounded-lg, pills, capsules. The system is square. This is the fastest possible tell.#666, #888, and text-gray-500 all read as a different brand.font-bold on an H1 is wrong.#6E6885.LEM. x.y plate numbers on non-Lemma work. Change the prefix.prefers-reduced-motion block. Every animated component on the site has one.Do not overcorrect into a sterile black-and-white wireframe either. Lemma's restraint is precise hierarchy, real evidence, exact alignment, and one accent used with intent. It is not the absence of design.
Run this against a finished page.
box-shadow. Should be zero.#1D1956 at 65%, or did a neutral gray sneak in?#755CFE: does it mark a state, a healthy path, a link, or a plate stroke? If it is marking importance, remove it.1px #D4D4DD rules rather than cards or background changes? Does the content column carry its left and right rails at xl?ease-out unless it is an overlay at 300ms? Does every animation have a prefers-reduced-motion: reduce block?#5D4EE7 underlined at 40% decoration opacity? Are the Tailwind Typography backticks and curly quotes stripped?h1, ordered headings, landmarks, a skip link, native controls, semantic tables, visible 2px #755CFE focus rings at 2px offset, WCAG AA contrast, and no meaning carried by color alone. Measured ratios against the #F2F2F2 page, so you do not have to guess:| Foreground | Ratio on #F2F2F2 | Verdict |
|---|---|---|
#1D1956 Admiral | 14.15:1 | Passes everything |
#5D4EE7 purple deep | 5.06:1 | Passes AA for body text. This is why links are this color and not Lady Blue |
#6C6991 muted | 4.61:1 | Passes AA for body text |
#755CFE Lady Blue | 3.98:1 | Large text and UI boundaries only. Fails AA for small body copy |
#EE5557 regression | 3.09:1 | Large text and UI boundaries only |
#A1A0B8 gray-tint-400 | 2.28:1 | Fails AA entirely. Lemma uses it for inert machine IDs. Never let it carry meaning alone |
White on #1D1956 is 15.84:1 and Admiral on #E8E8E8 is 12.93:1, so both button variants are clear.
The target is Lemma's judgment, not Lemma's decoration.
One type size does almost everything. Every visible run but four is 14px/20px, so hierarchy comes from colour, not size, and one gold word carries the whole editorial hand.
Act as an excellent Curated Supply designer and design engineer. The standard is that Justin Farrugia could not tell your page from one he shipped. That comes from four things and almost nothing else: Geist at 14px for nearly all text, a three-tone grey paper system (#f2f2f2 page, #ffffff plates, #141414 ink), pill geometry on every interactive element, and one gold word as the only hierarchy signal in an otherwise flat grid.
Curated Supply is a curated product-discovery site plus a weekly newsletter. Its editorial premise is "quiet luxury": well-made objects that endure, shown flat and unranked, with a $50 kettle scale next to a $270,000 car. The design does the same job the premise does. Nothing shouts, nothing is decorated, and taste is communicated by restraint rather than by chrome.
Everything below traces to live CSS extraction from www.curated.supply on September 18, 2026, read from four pages: / (Discover grid + hero), /products/studio-display (detail page), /about (long-form copy, contact form), and /browse. The site is built in Framer (--framer-* custom properties, --token-<uuid> color tokens, Framer form and search components). There is no published brand page, design.md, or style guide. Nothing here comes from Brandfetch, Dribbble, or any third-party kit.
Rules for using this file:
sans-serif elements and default-blue rgb(0,0,238) link colors on wrapper nodes. Those are Framer defaults, not design decisions. Every value in this file was read from visible, text-bearing or surface-bearing elements only.The About page states the standard in plain declaratives, and they are the design brief:
"This website exists because the world doesn't need more stuff, it needs better stuff."
"It's not about how things look, but how they endure. Not status objects, but standards. Not what they do, but how well it's done."
"Design that respects your time and attention."
The distinctive material choices, each one actionable:
14px / 20px Geist. The exceptions are exactly one 40px H1, one 16px subline, and the 24px/32px headings on detail pages. Hierarchy comes from color (#141414 vs #737373), not from size.Geist Mono Variable appears on exactly one thing: prices ($1,599), with letter-spacing: 0.14px. Never on labels, never on headings, never as decoration.#f2f2f2, plates #ffffff, ink #141414, secondary #737373, hairline #e2e2e2. There is not a single gradient, glow, or drop shadow. The only box-shadow on the site is a 1px inset ring used as a chip border.40px, buttons 28px, chips 32px. Plates (cards, image mounts) are 16px and 24px. Nothing is square, nothing is 4px or 6px.Staff Pick in #b99651 (with a small star glyph) sits top-left inside a card plate. There is no "featured" size, no accent border, no badge background. That single colored word is how the editor's hand shows.Brand · Category as two separate links in #737373, then Name in #141414 on the left and $Price in mono on the right. Every card is three affordances in one.This file governs work that should belong to the Curated Supply material family (a feed, a directory, a product grid, an editorial index) while being its own product. Borrow freely: the type rule, the three-grey paper system, pill geometry, the card anatomy, the Brand · Category meta idiom, the single-accent-word hierarchy, the flat unranked grid. Do not borrow: the "Curated Supply" wordmark, Justin's About copy, the category or list taxonomy, or the affiliate disclosure language.
#141414 on #f2f2f2 or #ffffff. Never drop below #737373 for text that must be read.--font-sans: "Geist Variable", "Geist", "Geist Placeholder", sans-serif;
--font-mono: "Geist Mono Variable", "Geist Mono", monospace;
The site loads Geist and Geist Mono from Google Fonts (fonts.gstatic.com/s/geist/v5/..., static 400 and 500 plus a variable face). A Framer search modal also loads Fragment Mono, which is never used in visible content and is not part of the system. Self-hosting Geist via geist npm or @fontsource-variable/geist is equivalent.
| Role | Size / line | Weight | Tracking | Color | Where |
|---|---|---|---|---|---|
| Display | 40px / 44px | 400 | -0.8px | #141414 | one H1 per page ("Discover well-designed, carefully curated products") |
| Section heading | 32px / 35.2px | 500 | -0.64px | #141414 | H3 on detail and about pages ("About", "Contact") |
| Detail price | 24px / 26.4px | 500 | normal | #141414 | H5 on product page |
| Lede | 16px / 24px | 400 | 0.16px | #737373 (about page uses #666666) | hero subline, product description |
| Body / UI | 14px / 20px | 400 | normal or 0.14px | #141414 primary, #737373 secondary | everything else |
| Button / active | 14px / 20px | 500 | 0.14px | #141414 or #ffffff | Subscribe, Purchase Link, active chip |
| Number | 14px / 20px mono | 400 | 0.14px | #141414 | prices in cards |
Rules:
-0.02em of their size). Body and UI carry none or +0.01em. Do not track body text negative.20px; for 16px exactly 24px. Both are 1.43 and 1.5 respectively, not a shared ratio.#737373 instead.Framer tokens as resolved on the live site. Names are descriptive; Framer stores them as UUIDs.
| Token (descriptive) | Value | Role |
|---|---|---|
paper | #f2f2f2 | page background, inactive chip fill, textarea fill on about |
paper-soft | #f5f5f5 | secondary surface (rare) |
plate | #ffffff | card plates, image mounts, input pills, "Show More" button |
plate-near | #fdfdfd | declared, not observed in use |
hairline | #e2e2e2 | footer rule, chip inset ring, disclosure rule |
hairline-soft | #e8e8e8, #ebebeb | declared secondary rules |
ink | #141414 | primary text, primary button fill |
ink-soft | #2e2e2e | declared, not observed in use |
muted | #737373 | secondary text, meta links, placeholder-level labels |
muted-deep | #666666 | about-page lede |
disabled | #cccccc | declared |
gold | #b99651 | Staff Pick text and star. The only chromatic color on the site. |
scrim | #0c0c0c33 | 20% ink overlay for modals |
Rules that govern usage:
color-scheme: normal, no dark tokens, no theme switch. The prefers-color-scheme: dark media query in the stylesheet is Framer boilerplate. Do not add a dark mode to belong here.#141414 vs #737373. Primary text and the primary action are ink. Everything supporting is muted. There is no third text tone in common use.#737373 to #141414 (--framer-link-hover-text-color: #141414, transition: color 0.4s). No underline appears on hover. Ink-colored links do not change on hover.| Radius | Use |
|---|---|
40px | text inputs (height 40px, so a full pill) |
32px | filter chips (height 36px, full pill) |
28px | buttons (height 36px to 40px, full pill) |
24px | large image plate on detail pages (1368 x 720 with 40px inner padding) |
16px | card plate, textarea |
8px | image inside a card plate |
corner-shape: superellipse(1) is Framer's default, which is a plain circular corner. No squircles. No 4px or 6px anywhere.
There is none. The only box-shadow on the site is the inactive chip ring:
box-shadow: rgb(226, 226, 226) 0px 0px 0px 1px inset; /* #e2e2e2 hairline, inset so layout does not shift */
Depth comes from #ffffff plates on #f2f2f2 paper and from nothing else. Borders are never used for cards. Inputs and buttons have no border and no ring at rest.
1368px.445.33px each. Card plate is 445 x 447.540px measure (x=450 at 1440). Newsletter form is 428px wide, centered.24px-radius white plate holding the hero image with 40px padding, then a 540px description column.#e2e2e2 hairline above the footer. No section backgrounds.<= 809.98px (phone), 810px to 1199.98px (tablet), >= 1200px (desktop).--ease: cubic-bezier(0.44, 0, 0.56, 1); /* symmetric ease-in-out */
--duration: 0.4s; /* 92 of 94 declared transitions */
--duration-fast: 0.3s; /* 2 declared */
Only color transitions on links were observed. No transform on hover, no scale on cards, no image zoom, no fade-in on scroll. The one keyframe on the site (__framer-blink-input) is a Framer search-modal internal. Reduced-motion is moot because nothing moves.
Unconfirmed: Framer components declare transition: all on wrappers; nothing was observed animating other than link color.
The most copied and most misapplied element. Recipe at 1440:
plate 445 x 447, #ffffff, radius 16px, overflow hidden, no shadow, no border
image link padding 24px 16px 8px; image mount 413 x 355, radius 8px, object-fit contain
(product on transparent or white, never a photo crop)
badge absolute 16px / 16px inside the image link: star glyph + "Staff Pick", 14px Geist 400, #b99651
meta row padding 0 16px 16px; flex row, gap 16px, justify space-between
left col flex column, gap 4px
line 1 "Brand" link #737373 · "Category" link #737373 (the dot is its own 14px #737373 text, gap 6px)
line 2 "Name" 14px #141414, single line, overflow clip
right "$1,599" 14px Geist Mono 400 #141414, tracking 0.14px
Judgment: the card is a plate, not a bordered box. The image gets its own 8px radius inside the 16px plate so the two radii read as nested. The name never wraps. If content has no price, leave the right column empty rather than inventing a number-shaped stand-in.
inactive height 36px, padding 8px 12px 8px 10px, #f2f2f2 fill, radius 32px,
inset ring 1px #e2e2e2, label 14px Geist Variable 400 #737373 tracking 0.14px
active same box, #ffffff fill, no ring, label #141414
The active chip is the white one on grey paper. Do not invert to a dark chip.
| Variant | Recipe | Label |
|---|---|---|
| Primary | #141414 fill, #ffffff text, radius 28px, padding 8px 12px, height 36px, gap 4px for a trailing arrow | "Purchase Link", "Send message" |
| Secondary | #ffffff fill, #141414 text, radius 28px, padding 8px 12px, height 40px | "Show More" |
| Inline text | transparent, #141414, 500, sits inside an input pill | "Subscribe" |
Labels are 14px, weight 500, tracking 0.14px. Sentence case ("Show More" is the one title-case exception on the site; prefer sentence case for new labels).
text input wrapper #ffffff, radius 40px, padding 10px 12px, height 40px, no border, no shadow
input 14px Geist 400 #141414, placeholder #737373 ("name@email.com", "Your name")
textarea #ffffff, radius 16px, height 148px
focus Framer sets outline: none and swaps in --framer-input-focused-* (values unset on this site),
so visually nothing changes. For accessibility, add a 1px inset ring in #141414 on
:focus-visible; this is the one place to improve on the source.
inline form the newsletter field holds a transparent "Subscribe" text button flush right inside the pill
Top bar: wordmark left, then "Discover / Browse / Blog / About" as 14px Geist Variable links, padding 6px 12px, height 36px, current page #141414, others #737373. Small centered eyebrow "Updated every Sunday" in #737373 above the H1. No sticky header, no blur, no border.
Hairline #e2e2e2 full width, then four columns of 14px links (Navigation, Info, Categories, Brands, Lists) with headings in #141414 and links in #737373, disclosure paragraph in #737373, then © 2026 ... · Made by @handle.
Discover, Browse, Blog, About, Subscribe, Show More, Purchase Link, Send message, Sort by: Featured, Staff Pick, All, Tech, Workspace, Home, Carry, Books./* Extracted from www.curated.supply on 2026-09-18. Framer site; token names are descriptive. */
:root {
color-scheme: light;
--font-sans: "Geist Variable", "Geist", sans-serif;
--font-mono: "Geist Mono Variable", "Geist Mono", monospace;
--paper: #f2f2f2;
--plate: #ffffff;
--hairline: #e2e2e2;
--ink: #141414;
--muted: #737373;
--muted-deep: #666666;
--gold: #b99651;
--scrim: rgba(12, 12, 12, 0.2);
--r-input: 40px;
--r-chip: 32px;
--r-button: 28px;
--r-plate-lg: 24px;
--r-plate: 16px;
--r-image: 8px;
--gutter: 36px;
--content-max: 1368px;
--grid-gap: 16px;
--ease: cubic-bezier(0.44, 0, 0.56, 1);
--duration: 0.4s;
}
html { background: var(--paper); color: var(--ink); font: 400 14px/20px var(--font-sans); -webkit-font-smoothing: antialiased; }
h1 { font-size: 40px; line-height: 44px; font-weight: 400; letter-spacing: -0.8px; margin: 0; }
h3 { font-size: 32px; line-height: 35.2px; font-weight: 500; letter-spacing: -0.64px; margin: 0; }
.lede { font-size: 16px; line-height: 24px; letter-spacing: 0.16px; color: var(--muted); }
.muted { color: var(--muted); }
.num { font-family: var(--font-mono); letter-spacing: 0.14px; }
a { color: inherit; text-decoration: none; transition: color var(--duration) var(--ease); }
a.meta { color: var(--muted); }
a.meta:hover { color: var(--ink); }
/* Card */
.card { background: var(--plate); border-radius: var(--r-plate); overflow: hidden; display: flex; flex-direction: column; }
.card__media { position: relative; padding: 24px 16px 8px; }
.card__img { display: block; width: 100%; aspect-ratio: 413 / 355; object-fit: contain; border-radius: var(--r-image); }
.card__badge { position: absolute; top: 16px; left: 16px; display: flex; gap: 6px; align-items: center; color: var(--gold); }
.card__meta { display: flex; justify-content: space-between; gap: 16px; padding: 0 16px 16px; }
.card__meta-left { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.card__kicker { display: flex; gap: 6px; color: var(--muted); }
.card__title { color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Chip */
.chip { height: 36px; padding: 8px 12px 8px 10px; border-radius: var(--r-chip); background: var(--paper);
box-shadow: inset 0 0 0 1px var(--hairline); color: var(--muted); letter-spacing: 0.14px; border: 0; font: inherit; }
.chip[aria-pressed="true"] { background: var(--plate); box-shadow: none; color: var(--ink); }
/* Buttons */
.btn { height: 36px; padding: 8px 12px; border-radius: var(--r-button); border: 0; font: inherit; font-weight: 500; letter-spacing: 0.14px; display: inline-flex; align-items: center; gap: 4px; }
.btn--primary { background: var(--ink); color: var(--plate); }
.btn--secondary { background: var(--plate); color: var(--ink); height: 40px; }
.btn--inline { background: transparent; color: var(--ink); padding: 0; height: 20px; }
/* Input pill */
.field { display: flex; align-items: center; height: 40px; padding: 10px 12px; background: var(--plate); border-radius: var(--r-input); }
.field input { flex: 1; min-width: 0; border: 0; background: transparent; font: inherit; color: var(--ink); outline: none; }
.field input::placeholder { color: var(--muted); }
.field:focus-within { box-shadow: inset 0 0 0 1px var(--ink); } /* addition for a11y; source has no visible focus */
.textarea { background: var(--plate); border-radius: var(--r-plate); padding: 10px 12px; border: 0; font: inherit; }
/* Grid */
.grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--grid-gap); }
@media (max-width: 1199.98px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 809.98px) { .grid { grid-template-columns: 1fr; } :root { --gutter: 16px; } }
hr { border: 0; border-top: 1px solid var(--hairline); margin: 0; }
#141414 against #737373 at the same weight.object-fit: contain, inside its own 8px radius.#141414 or #737373 (plus #666666 lede on long-form). No opacity-based greys.box-shadow except the chip's 1px inset hairline. Zero gradients.Brand · Category muted links, name ink, number mono right.mal.gg/yc is a directory of 11,704 Y Combinator founders with 20 filter dimensions, three view modes and 586 pages of results, and the largest text anywhere on it is 20px. That restraint is the whole system. This file tells you how to build something that would sit next to it without looking louder, softer or more decorated.
The governing idea: the data is the interface. Chrome earns its pixels by encoding a fact. A color means a season. A count means how many rows an option would return. A percentage means how much of the dataset actually has that field. Nothing is styled to look considered. It looks considered because nothing is styled without a reason.
Extracted live from https://www.mal.gg/yc on 2026-09-21 by Aside, using skills/user/design-system-extraction/scripts/extract-tokens.js, plus targeted getComputedStyle probes and raw reads of the served Tailwind chunks (/_next/static/immutable/chunks/40ottiodm84ga.css and siblings).
Pages sampled: /yc (table view), /yc?view=cards, /yc?view=people, /yc?q=zzzzqqqq (empty state), /yc?page=300 and /yc?q=X25 (batch color coverage), and / (the author's personal homepage, which is a different design layer, see below).
No official guideline exists. /design.md, /llms.txt, /brand and /style-guide all return 404. There is no published design system, no brand page, no engineering blog. Every value in this file was read off the live DOM or the served CSS. Nothing here is from a third-party brand kit, and you should not trust one if you find it.
Built by Mal, lead project designer and co-founder of Keitoto (keitoto.com), a design and development studio. The /yc header credits "Built by Keitoto". Stack is Next.js on Vercel, Tailwind CSS v4, next/font for Geist, CSS Modules for the font variable classes.
The live site wins on conflict. This file is a dated snapshot. Re-extract if the site visibly diverges.
Six choices, each one actionable:
.yc-card (plate) and .yc-panel (container). Every surface on the page is one of those two. There is no third.yc-fade-in, 120ms, 2px of travel. Hover is an instant background swap with no transition at all.≈. A coverage meter above the results states what fraction of the current result set actually has each field.When two rules conflict, the earlier one wins.
.yc-card or .yc-panel. Do not invent a third surface treatment to solve a layout problem.--font-yc-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
--font-yc-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, SFMono-Regular, Menlo, monospace;
Both are real webfonts, loaded through next/font as variable faces (weight range 100 900), served self-hosted from /_next/static/immutable/media/*.woff2. Confirmed loaded at runtime via document.fonts.check().
A trap worth knowing: the site-wide Tailwind default is still --font-sans: "Inter", inherited from the author's personal homepage, and <body> still computes to Inter. Nothing inside /yc uses it. Every element resolves to Geist because the directory applies font-yc-sans / font-yc-mono explicitly. If you copy this, set your root font to Geist and skip the dead Inter declaration.
Type scale. Measured by frequency across the live table view:
| Size | Count | Role |
|---|---|---|
| 12px | 645 | The default. Table cells, buttons, filter rows, menu items, search input, everything. |
| 14px | 350 | Emphasis inside a row. The founder's name, the company name, the industry cell. |
| 11px | 67 | Mono metadata. Batch codes (W27), X handles (@cancelik). |
| 16px | 34 | The one paragraph of running prose per surface, and the search icon box. |
| 13px | 20 | Avatar initials. |
| 15px | 20 | Rare. Secondary headings inside panels. |
| 10px | 2 | The toggle sub-count under a chip label. |
| 20px | 1 | The page H1. Only instance on the site. |
Weights are 400 and 500. That is the system. 616 elements at 400, 521 at 500, and exactly 2 at 600 (the H1). Never use 700. Never use a weight below 400.
Line height equals font size almost everywhere. 647 elements at line-height: 12px against 645 at font-size: 12px. This is Tailwind's leading-none, applied deliberately: every label and cell is a single line, so leading is dead space. The exceptions are 24px (13 elements, the one block of explanatory prose) and 19.6px (4 elements, a 14px paragraph at 1.4).
Letter spacing is a real token here, not a garnish.
/* body and UI text, 475 elements */ letter-spacing: -0.1px;
/* the H1, once */ letter-spacing: -0.2px;
/* panel section labels, 10 elements */ letter-spacing: 0.72px;
Negative tracking on everything small, positive tracking only on the uppercase section labels. Mono text carries normal tracking and is never tracked out.
Light only. Zero occurrences of prefers-color-scheme across all three served stylesheets, zero .dark selectors, no theme toggle, color-scheme: normal. There is no dark mode to copy, and adding one is not "completing" the system. If you need dark, you are designing it yourself and it will not be theirs.
Neutrals. Note that the darkest ink is #0a0a0a and the page is #fafafa. Neither is pure.
--yc-text-primary: #0a0a0a; /* 496 elements. Row content, headings. */
--yc-text-secondary: #5b5f66; /* 41. Inactive segmented labels. */
--yc-text-tertiary: #8a8f98; /* 78. Handles, section labels, ellipses. */
--yc-text-muted: #737373; /* 419. Column headers, secondary cells, kbd. */
--yc-text-link: #1e46d6;
--yc-bg-page: #fafafa; /* the only page background */
--yc-bg-surface: #ffffff; /* every plate and panel */
--yc-bg-subtle: #f5f6f7; /* table header row, segmented control track */
--yc-bg-hover: #f3f4f6; /* the only hover state in the product */
--yc-bg-selected: #eff3ff;
--yc-bg-row-selected: #eef0f3;
--yc-border-default: #e4e5e8; /* panel borders, control borders, the shadow ring */
--yc-border-strong: #d3d5da; /* scrollbar thumb, checkbox outlines */
--yc-border-subtle: #efeff1; /* row dividers inside a panel */
Three ink greys carry 95% of the text. Three borders carry every division. Learn the difference between --yc-border-default (#e4e5e8, separates a surface from the page) and --yc-border-subtle (#efeff1, separates a row from its neighbour inside a surface). Getting that backwards is the fastest way to look wrong.
Accents. Four, and they are not interchangeable.
--yc-accent-indigo: #5b5bf5; /* primary brand accent */
--yc-accent-indigo-soft: #efeefe;
--yc-accent-blue: #2c5bff;
--yc-accent-focus: #3177f0; /* focus rings and counts only */
The six-tone semantic palette. Every tone ships with a soft companion. This pairing is the whole badge, chip and avatar system.
| Tone | Foreground | Soft background |
|---|---|---|
| green | #0e8f5e | #e9f7f0 |
| gold | #b7791f | #fdf4e3 |
| blue | #2563eb | #eaf1fe |
| orange | #d0622b | #fdf0e8 |
| pink | #ce3560 | #fdebf0 |
| purple | #6a59cc | #f0edfb |
Six hues, well separated, no near-duplicates. There is no red and no yellow. There is no scale within a tone (no green-100 through green-900). One foreground and one background per tone, and that is the entire ramp.
Batch season colors are a separate hardcoded set, and this is deliberate. They are written as Tailwind arbitrary values (text-[#a560f5]), not tokens, because they belong to the dataset rather than the interface:
W (Winter) #3177f0 blue
S (Summer) #d67f02 amber
F (Fall) #a560f5 purple
X (Spring) #0e8f5e green
Verified across batches S05 through W27. The point to steal: a data-derived color mapping is not part of your theme. Keep it out of your token file so nobody reuses "batch purple" as a UI accent.
Where color is spent, in full. Nowhere else.
border-yc-green bg-yc-green-soft text-yc-green).#1e46d6, underline on hover.#3177f0.No gradients. No glows. No colored shadows. Zero gradient tokens resolve to anything other than transparent.
--radius-sm: 0.25rem; /* 4px */
--radius-md: 0.375rem; /* 6px */
--radius-lg: 0.5rem; /* 8px */
--radius-xl: 0.75rem; /* 12px */
--radius-2xl: 1rem; /* 16px */
Actual usage, ranked: 8px (42 elements), 9999px (40), 12px (35), 16px (2), 10px (1), 6px (1).
The rule that falls out of that:
.yc-card: buttons, popovers, chips, search field, segmented track, feed cards.No squircles, no corner smoothing. corner-shape computes to superellipse(1), which is the browser default, not a design decision. Nothing on this site is sharp-cornered.
Three recipes, verbatim.
/* Plate. The ring IS the border. */
--yc-shadow-card:
0 1px 2px 0 #5258660f,
0 0 0 1px #e4e5e8,
0 1px 2px 0 #0000000a,
0 1px 2px 0 #00000005;
/* Floating chrome. Same ring, real depth. */
--yc-shadow-popover:
0 12px 32px -8px #0f17292e,
0 0 0 1px #e4e5e8,
0 2px 6px 0 #0000000f;
/* Panel. A real 1px border plus the softest possible lift. */
box-shadow: 0 3px 6px -1px rgba(0,0,0,0.03), 0 1px 2px rgba(0,0,0,0.04);
border: 1px solid var(--yc-border-default);
The technique to take away: a plate does not get a border. It gets a 0 0 0 1px spread shadow in --yc-border-default, stacked with three near-invisible 1px blurs. The ring is crisp at any radius, never doubles against a neighbouring border, and costs no layout box. A panel, which is a real container rather than a floating object, gets an actual border instead.
Two more, both tiny:
/* Toolbar chips. The entire elevation of a 36px control. */
filter: drop-shadow(0 1px 1px rgba(15,23,41,0.05));
/* Focus. Inset, sub-pixel, no halo. */
box-shadow: inset 0 0 0 0.8px rgba(49,119,240,0.4);
Focus is an inset ring. It never grows the element and never bleeds outside it. Copy this.
Scrollbars are styled and it matters at this density:
.yc-thin-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
.yc-thin-scroll::-webkit-scrollbar-thumb { background: var(--yc-border-strong); border-radius: 999px; }
.yc-thin-scroll::-webkit-scrollbar-track { background: transparent; }
main mx-auto max-w-[1440px] px-8 py-8 flex flex-col gap-4
mobile: px-4 py-5
body row flex items-start gap-4
mobile: flex-col items-stretch
sidebar .yc-panel w-[340px] shrink-0 self-start overflow-hidden
mobile: hidden
results .yc-panel flex-1 min-w-0 overflow-hidden
At a 1440px viewport: 1376 content, 340 filter panel, 16 gap, 1020 results panel.
Everything is 16px. Page gutter is 32, every gap between blocks is 16, padding inside a card is 16, gaps inside a row are 12, gaps inside a cell are 8 or 6. Tailwind's --spacing: 0.25rem base, used almost exclusively at 4 (16px) and 3 (12px).
Sections do not separate with whitespace. They separate by being a panel. There is one vertical rhythm on the page and it never breathes; density is the point.
The table grid is explicit and fixed:
grid-template-columns:
32px /* avatar */
minmax(220px, 1.4fr) /* founder + company */
120px 120px 130px /* role, batch, industry */
minmax(150px, 1fr) /* location */
72px /* team */
minmax(160px, 0.9fr); /* X handle */
min-width: 1000px;
Fixed columns for short enumerable values, minmax only for free text. The container scrolls horizontally with .yc-thin-scroll rather than letting columns collapse.
Row metrics:
| Row | Height | Background | Divider | Type |
|---|---|---|---|---|
| Header | 48px (h-12) | #f5f6f7 | bottom #e4e5e8 | 12px / 500 / -0.1px / #737373 |
| Data | 64px (h-16) | #ffffff | bottom #efeff1 | 12px / 400 |
Both at px-4. Hover on a data row sets background to #f3f4f6 and changes nothing else.
Breakpoints are Tailwind v4 defaults (40/48/64/80/96rem). The directory only ever uses md (768px) and xl (1280px), plus max-md: for the mobile layer. Below md the filter panel is hidden entirely and the table is replaced by the People list.
Three view modes, one dataset:
grid grid-cols-1 gap-3 p-4 md:grid-cols-2 xl:grid-cols-3, each card .yc-card rounded-xl p-4 flex flex-col gap-3 hover:bg-yc-hover, about 160px tall.flex items-center gap-3 px-4 py-3 border-b border-yc-line-subtle. The densest of the three, and the one that survives on mobile.The entire motion budget of the site:
@keyframes yc-fade-in {
0% { opacity: 0; transform: translateY(2px); }
100% { opacity: 1; transform: none; }
}
.yc-fade-in { animation: 0.12s ease-out yc-fade-in; }
120 milliseconds. Two pixels. One easing curve. One keyframe in the entire stylesheet. It is applied to popovers and dropdowns on open, and to nothing else.
There is no hover transition. --default-transition-duration: 0.15s and --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1) exist as Tailwind defaults and are not used on any interactive element. A row hover is an instant background swap. This reads as fast rather than unfinished, because at 64px rows a 150ms crossfade on every row you sweep past is visual noise.
Hover states are gated behind @media (hover: hover), so nothing flickers on touch.
Reduced motion is handled by forcing entrance opacity to 1:
@media (prefers-reduced-motion: reduce) {
[data-page-motion].js [data-motion] { opacity: 1; }
}
No skeletons, no spinners, no shimmer anywhere in the CSS.
.yc-card {
background: var(--yc-bg-surface);
box-shadow: var(--yc-shadow-card);
border-radius: 12px;
}
.yc-panel {
background: var(--yc-bg-surface);
border: 1px solid var(--yc-border-default);
border-radius: 16px;
box-shadow: 0 3px 6px -1px rgba(0,0,0,0.03), 0 1px 2px rgba(0,0,0,0.04);
}
Panel for a region of the page that owns scroll and contains rows. There are two: the filter sidebar and the results container. Card for everything that floats, toggles, or is a discrete object: buttons, kbd keys, the search field, the segmented control track, popovers, feed cards.
If you find yourself writing a third surface class, you have a layout problem, not a material problem.
| Height | Class | Radius | Use |
|---|---|---|---|
| 40px | h-10 | 12 | Toggle chips in the toolbar (On X, Hiring now, One contact per company) |
| 36px | h-9 | 12 | Outer controls: search field, sort button, filter counter, segmented track |
| 28px | h-7 | 8 (12 when selected) | Items inside a segmented control |
| 24px | h-6 | 12 | Kbd keys and inline mono badges |
| 20px | size-5 | 8 | Checkboxes |
flex h-9 items-center gap-2 rounded-xl
border border-yc-line bg-yc-surface px-2.5
text-[12px] leading-none
drop-shadow-[0_1px_1px_rgba(15,23,41,0.05)]
hover:bg-yc-hover
12px / 400 / #0a0a0a on white, 1px #e4e5e8 border. Hover changes background only. No border change, no shadow change, no lift.
inactive: h-10 rounded-xl border border-yc-line bg-yc-surface
pl-4 pr-3 text-[12px] leading-3 text-yc-ink-2
active: border-yc-green bg-yc-green-soft text-yc-green
This is the most transferable idea in the component set. An active filter does not turn "brand color". It adopts the semantic tone of the thing it filters. Hiring now goes green because hiring is a positive-availability fact. Border, background and text all shift to the same tone family at once, so the chip reads as a single colored object rather than a bordered box with colored text inside it.
Each chip carries its live count in Geist Mono at 10px or 12px in --yc-text-secondary, directly under or beside the label.
track: .yc-card h-9 bg-yc-subtle p-1 flex items-center
item: h-7 rounded-lg px-2 text-[12px] font-medium leading-none gap-2
selected: .yc-card text-yc-ink (white plate rises out of the grey track)
unselected: text-yc-ink-2 hover:text-yc-ink
The selected item is literally the same .yc-card material as everything else. The track is --yc-bg-subtle. Selection is a plate on a recess, with no indicator, no underline, no pill animation.
wrapper: .yc-card flex h-9 items-center justify-between py-1 pl-3 pr-2
icon: 16px SVG, leading
input: min-w-0 flex-1 bg-transparent text-[12px] leading-none tracking-[-0.1px]
text-yc-ink outline-none placeholder:text-yc-ink-muted
kbd: .yc-card h-6 rounded-lg px-2 font-yc-mono text-[12px] font-medium
text-yc-ink-muted, hidden below md
The input carries no border, no background and no radius of its own. The wrapper is the control. The input is just text.
.yc-fade-in absolute right-0 top-[calc(100%+6px)] z-40
w-[340px] rounded-xl border border-yc-line bg-yc-surface overflow-clip
box-shadow: var(--yc-shadow-popover)
mobile: fixed inset-x-4 bottom-4 top-auto w-auto
340px, the same width as the filter panel. One width constant governs both pieces of floating and docked chrome. The 6px offset from the trigger is the only place a non-4-multiple spacing value appears.
Section labels inside a popover:
12px / 500 / letter-spacing 0.72px / color #737373 or #8a8f98
text-transform: none /* the copy is literally uppercase in the markup */
inline-flex shrink-0 items-center justify-center rounded-full font-medium
size 32px, text 13px / 500
bg-yc-{tone}-soft text-yc-{tone}
Two-letter initials on a soft tone background with the matching tone foreground, cycling through all six tones. No images, no image fallback chain, no gradient fill. At 11,704 records this is the correct call: consistent, instant, and it never breaks.
batch pill: .yc-card inline-flex h-6 items-center rounded-lg px-2
font-yc-mono text-[12px] font-medium, color = season color
batch code: font-yc-mono text-[11px] font-medium, color = season color (no plate)
handle: shrink-0 font-yc-mono text-[11px] text-yc-ink-3
count: font-yc-mono text-[12px] font-medium tracking-[-0.1px]
Untitled UI, served as individual SVG files from /yc/figma/<name>.svg. Sizes: 16px in the search field, 14px in buttons and toolbar controls, 12px for the command glyph. No CSS filters, no opacity tricks, no icon font. The SVG carries its own stroke color.
Representative names, useful if you are matching the set: search-md, grid-01, user-01, list, switch-vertical-01, filter-funnel-02, columns-03, star-05, refresh-cw-01, marker-pin-01, chevron-selector-vertical, x-close.
Sits above the results, right-aligned, and it is the signature component of the whole product.
flex items-center gap-2 text-[12px] font-medium leading-none
label: "Known for these results" in --yc-text-tertiary
pairs: <field name> in ink + <percentage> in Geist Mono
Rendering as Known for these results Role 41% Bio 78% Ex-company 20% School 25%, recomputed against the current filter set, each pair carrying a tooltip that explains the gap in plain language.
Build this. Any interface over a scraped, parsed or merged dataset should state its own coverage. It converts "this directory is incomplete" from a thing users discover into a thing the product says first.
mal.gg runs two systems from one stylesheet, and the split is instructive.
The personal homepage (/) is a card-stack collage on a dotted board. Inter at 16px, a single Gasoek One wordmark ("MAL", 16px, #d9d9d9), white cards at a 2px radius scattered across --color-board: #fafafa with --color-board-dot: #e6e6e6. It has its own token family: --color-card, --color-ink, --color-hairline, --color-rule, --card-scale, --logo-scale.
The directory (/yc) overrides all of it. Geist instead of Inter, the --yc-* namespace instead of --color-*, 12px instead of 16px, radius 12/16 instead of 2.
The lesson: a tool inside a personal site is allowed to be a different design system. Mal did not force the directory to wear the homepage's collage language. He gave it a scoped namespace and let it be a product. If you are building a tool inside a portfolio, do the same, and name your tokens with a prefix so the boundary is visible in the CSS.
Sentence case everywhere, with two deliberate exceptions.
Table column headers are literal uppercase strings in the markup, not text-transform: FOUNDER, ROLE, BATCH, INDUSTRY, LOCATION, TEAM, X_url. Note X_url. It is the raw database field name, snake case and all, sitting in production next to six clean words. That is not sloppiness, it is the same honesty move as the coverage meter: this is a view over a table, and the table has a column called x_url.
Panel section labels are uppercase strings with 0.72px tracking: SORT BY, Company · Lifecycle, Founder · Background. The middot separates the subject from the facet, and it is the site's one piece of ornament.
Every option carries its count. Batch 49, Tags 337, Ex-company 148, Top company 91, Has GitHub 1,182. Approximations are prefixed with ≈: AI (text) ≈2,274, Agents ≈437.
Options get a one-line mechanic, not a benefit. From the sort menu:
Top companies first 91 top companies
Most reachable personal handle + bio + known role
Relevance needs a search query
Lowercase, no punctuation, states what the sort actually does. "Most reachable" would be marketing on its own; personal handle + bio + known role makes it a definition.
Tooltips explain the data, not the UI. The Role tooltip reads: "Role is parsed from founder titles in yc.db. Known for 41% of these rows, the rest have no title at all, mostly 2019 to 2023. Those rows stay in the result and are counted as Unknown, never as No." It names the source table, admits the gap, dates the gap, and states the semantics of a missing value. No interface has ever needed to explain a hover target this well and that is exactly why it works.
The empty state is the best copy on the site. It does four things in sequence:
No founders match this filter
Dimensions combine with AND, values inside one dimension with OR.
Loosening one dimension brings results back, each option below is
counted against the rest of your filters.
Drop the search "zzzzqqqq" 11,704 results [Apply]
Keep every filter, remove the free-text match.
[Clear all filters] [Reset to newest batch]
Name the state, explain the boolean semantics that caused it, offer specific escape hatches with the row count each one would restore, then the blunt resets. Nobody writes empty states this way. Do it.
No em dashes, no exclamation marks, no emoji, no sentences addressed to the user. The product never says "we", "you", "let's" or "oops".
Drop-in tokens using the source site's own naming, so a future live inspection maps one to one onto this file.
/* mal.gg /yc design tokens. Extracted 2026-09-21. Light only, by design. */
:root {
/* Type */
--font-yc-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif;
--font-yc-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, SFMono-Regular, Menlo, monospace;
/* Ink */
--yc-text-primary: #0a0a0a;
--yc-text-secondary: #5b5f66;
--yc-text-tertiary: #8a8f98;
--yc-text-muted: #737373;
--yc-text-link: #1e46d6;
/* Surface */
--yc-bg-page: #fafafa;
--yc-bg-surface: #ffffff;
--yc-bg-subtle: #f5f6f7;
--yc-bg-hover: #f3f4f6;
--yc-bg-selected: #eff3ff;
--yc-bg-row-selected: #eef0f3;
/* Line */
--yc-border-default: #e4e5e8;
--yc-border-strong: #d3d5da;
--yc-border-subtle: #efeff1;
/* Accent */
--yc-accent-indigo: #5b5bf5;
--yc-accent-indigo-soft: #efeefe;
--yc-accent-blue: #2c5bff;
--yc-accent-focus: #3177f0;
/* Semantic tones. One foreground, one soft background, no ramp. */
--yc-tone-green: #0e8f5e; --yc-tone-green-soft: #e9f7f0;
--yc-tone-gold: #b7791f; --yc-tone-gold-soft: #fdf4e3;
--yc-tone-blue: #2563eb; --yc-tone-blue-soft: #eaf1fe;
--yc-tone-orange: #d0622b; --yc-tone-orange-soft: #fdf0e8;
--yc-tone-pink: #ce3560; --yc-tone-pink-soft: #fdebf0;
--yc-tone-purple: #6a59cc; --yc-tone-purple-soft: #f0edfb;
/* Elevation */
--yc-shadow-card:
0 1px 2px 0 #5258660f,
0 0 0 1px #e4e5e8,
0 1px 2px 0 #0000000a,
0 1px 2px 0 #00000005;
--yc-shadow-popover:
0 12px 32px -8px #0f17292e,
0 0 0 1px #e4e5e8,
0 2px 6px 0 #0000000f;
--yc-shadow-panel:
0 3px 6px -1px rgba(0,0,0,0.03),
0 1px 2px rgba(0,0,0,0.04);
--yc-shadow-chip: 0 1px 1px rgba(15,23,41,0.05);
/* Shape */
--yc-radius-inner: 8px;
--yc-radius-plate: 12px;
--yc-radius-panel: 16px;
/* Metrics */
--yc-panel-width: 340px;
--yc-row-height: 64px;
--yc-header-height: 48px;
}
html { background: var(--yc-bg-page); color-scheme: light; }
body {
font-family: var(--font-yc-sans);
font-size: 12px;
line-height: 1;
letter-spacing: -0.1px;
color: var(--yc-text-primary);
}
/* The two primitives. There is no third. */
.yc-card {
background: var(--yc-bg-surface);
box-shadow: var(--yc-shadow-card);
border-radius: var(--yc-radius-plate);
}
.yc-panel {
background: var(--yc-bg-surface);
border: 1px solid var(--yc-border-default);
border-radius: var(--yc-radius-panel);
box-shadow: var(--yc-shadow-panel);
}
.yc-thin-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
.yc-thin-scroll::-webkit-scrollbar-thumb { background: var(--yc-border-strong); border-radius: 999px; }
.yc-thin-scroll::-webkit-scrollbar-track { background: transparent; }
/* The only animation. */
@keyframes yc-fade-in {
from { opacity: 0; transform: translateY(2px); }
to { opacity: 1; transform: none; }
}
.yc-fade-in { animation: 0.12s ease-out yc-fade-in; }
@media (prefers-reduced-motion: reduce) {
.yc-fade-in { animation: none; opacity: 1; }
}
/* --- sample components --- */
/* Toolbar control */
.yc-control {
display: flex; align-items: center; gap: 8px;
height: 36px; padding: 0 10px;
border: 1px solid var(--yc-border-default);
border-radius: var(--yc-radius-plate);
background: var(--yc-bg-surface);
filter: drop-shadow(var(--yc-shadow-chip));
font-size: 12px; line-height: 1;
color: var(--yc-text-primary);
}
@media (hover: hover) {
.yc-control:hover { background: var(--yc-bg-hover); } /* no transition, on purpose */
}
.yc-control:focus-visible {
outline: none;
box-shadow: inset 0 0 0 0.8px rgba(49,119,240,0.4);
}
/* Toggle chip: active state adopts the tone of what it filters */
.yc-chip[data-active="true"][data-tone="green"] {
border-color: var(--yc-tone-green);
background: var(--yc-tone-green-soft);
color: var(--yc-tone-green);
}
/* Table */
.yc-table-header {
height: var(--yc-header-height);
background: var(--yc-bg-subtle);
border-bottom: 1px solid var(--yc-border-default);
padding: 0 16px;
font-size: 12px; font-weight: 500; letter-spacing: -0.1px;
color: var(--yc-text-muted);
}
.yc-table-row {
height: var(--yc-row-height);
background: var(--yc-bg-surface);
border-bottom: 1px solid var(--yc-border-subtle);
padding: 0 16px;
font-size: 12px;
}
@media (hover: hover) {
.yc-table-row:hover { background: var(--yc-bg-hover); }
}
/* Every numeral, everywhere */
.yc-num {
font-family: var(--font-yc-mono);
font-size: 12px; font-weight: 500;
letter-spacing: -0.1px;
font-variant-numeric: tabular-nums;
}
Note: font-variant-numeric: tabular-nums is my addition, not theirs. Geist Mono is already monospaced so it is a no-op there, but it protects you if you ever fall back to the sans for a numeral.
line-height: 1 on labels and cells is correct here. Only running prose gets 1.4 or more.0 0 0 1px #e4e5e8 ring inside --yc-shadow-card. Reserve real borders for panels and controls.#0a0a0a, the page is #fafafa.minmax the free-text ones, and scroll horizontally with a 6px thin scrollbar.≈.Run this against a finished page.
normal..yc-card or .yc-panel. If there is a third, justify it or delete it.#e4e5e8. Row dividers use #efeff1. No doubled borders anywhere.transition. The only animation should be a 120ms popover fade. Hover must be instant.#f3f4f6 and nothing else. No lift, no shadow change, no border change, no content reveal.0 0 0 0.8px rgba(49,119,240,0.4) ring. It does not grow the element.MAL Gasoek One wordmark, or any implication of authorship. The tokens, geometry and component recipes are free to borrow. The identity is not.Act as a Luma designer and design engineer. Make the artifact feel like it was built by the same small team that builds luma.com: warm, soft, quietly playful, and obsessively finished. Shape the experience and the interface together; do not restyle a generic SaaS layout with pink accents and call it done.
There is no official Luma brand kit, press page, or published design system. Everything in this document was extracted from luma.com's production CSS and live pages (.theme-root token system, .lux-button components, event theme pages) in August 2026, plus Luma's own help docs (help.luma.com) and founder writing. Third-party "Luma brand kits" on Brandfetch, Dribbble, or Figma Community are guesses; do not use them. When this document and the live site disagree, the live site wins: inspect luma.com and update this file.
Luma is an events platform. Its tagline is "Delightful events start here." Its founder has written that the team "cares tremendously about delivering a delightful experience" and that most of its CSS and design is done in house by a deliberately small team. The product carries that philosophy visibly:
A Luma-ecosystem project should feel like a good neighbor to the product: a visitor moving from luma.com to your page should feel no texture change.
These projects are community and ambassador work, not official Luma surfaces. Borrow the material language (tokens, geometry, softness, motion), not the identity:
The Luma spark, for reference when linking back to Luma, is a four-cusp star built from circle arcs, drawn in currentColor:
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 133 134">
<path fill="currentColor" d="M133 67C96.282 67 66.5 36.994 66.5 0c0 36.994-29.782 67-66.5 67 36.718 0 66.5 30.006 66.5 67 0-36.994 29.782-67 66.5-67"/>
</svg>
When requirements compete, protect them in this order:
Luma loads no webfont for its UI. Use the exact stacks:
--font: -apple-system, BlinkMacSystemFont, "Apple Color Emoji", Inter, Roboto,
"Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif;
--mono-font: "SF Mono", Menlo, Monaco, Consolas, "Courier New", Courier, monospace;
This is a load-bearing brand decision. Loading Inter, Geist, or any grotesk as a webfont makes the page instantly feel foreign next to Luma on a Mac (where Luma renders in San Francisco). Let the OS decide.
Type scale (rem-based, 16px root):
| Token | Size | Use |
|---|---|---|
--font-size-xxxs | 0.625rem (10px) | fine print, badges |
--font-size-xxs | 0.75rem (12px) | metadata, timestamps |
--font-size-xs | 0.8125rem (13px) | secondary UI text |
--font-size-sm | 0.875rem (14px) | labels, section subtitles, small buttons |
--font-size-md | 1rem (16px) | body, inputs, medium buttons |
--font-size-lg | 1.125rem (18px) | section titles |
--font-size-xl | 1.25rem (20px) | large titles |
--font-size-xxl | 1.375rem (22px) | page/tab titles |
--font-size-xxxl | 1.5rem (24px) | large page titles |
Display sizes go beyond the scale (in-app page titles render around 28px, event titles up to 38px) but the weight never exceeds 600.
Weights: 300 light, 400 regular, 500 medium, 600 bold. Bold is 600, never 700. Buttons and labels use 500; titles use 600. Line heights: 1.2 titles, 1.3 reduced/compact, 1.5 body default.
Neutrals first. Luma's black is warm-dark, not pure:
--white: #fff;
--black: rgb(21, 21, 21); /* #151515: the dark-mode canvas and light-mode text */
--pure-black: #000; /* reserved; almost never a surface */
Gray ramp: 10 #f8f8f8, 20 #ececec, 30 #e0e0e0, 40 #d4d4d4, 50 #b5b5b5, 60 #959595, 70 #757575, 80 #555, 90 #353535, 100 #232323.
Accent families, each a 5..90 ramp (50 shown, the light-mode default step):
| Family | 50 | Note |
|---|---|---|
| cranberry | #f31a7c | the brand accent; --brand-color resolves here |
| barney | #ab46dd | violet (yes, it is really named barney) |
| purple | #682fff | |
| blue | #146aeb | |
| green | #3cbd2c | success |
| yellow | #eaab26 | warning (light mode uses the 60 step #d69712) |
| orange | #f8712b | |
| red | #ed2b32 | error |
Full cranberry ramp, since it is the brand: 5 #fef4f9, 10 #fde2ef, 20 #fcc6de, 30 #f98dbe, 40 #f6539d, 50 #f31a7c, 60 #d5176d, 70 #b6145d, 80 #98104e, 90 #790d3e. Washes: pale = accent at ~13% alpha (#f31a7c22), faint = ~7% (#f31a7c11).
Semantic mapping:
| Role | Light | Dark |
|---|---|---|
| Page background | gray-10 #f8f8f8 | #151515 |
| Raised surface | white | gray-100 #232323 |
| Text primary | #151515 | white |
| Text secondary | gray-70 | gray-40 |
| Text tertiary | gray-50 | gray-60 |
| Accent step | family-50 | family-30 (lighter, brighter) |
| Divider | black at 8% | white at 8% |
Two rules inside that table matter most for belonging:
#151515. Surfaces become white-at-low-opacity instead of solid grays.The brand gradient (logo moments and rare hero accents only, never section backgrounds):
/* light */ linear-gradient(-45deg, #8a18a8 0%, #ce2756 51.6%, #e7a90d 100%);
/* dark */ linear-gradient(-45deg, #d118ff 0%, #f32861 51.6%, #ffbe19 100%);
/* homepage logotype (radial): #ab46dd -> #f31a7c 36% -> #f8712b 51% -> #eaab26 77% */
Radius scale: 0.25rem small, 0.5rem default, 1rem large, 1.5rem cards. Modals use 1rem. Avatars and round buttons are full circles.
Luma applies squircle corner smoothing on top of the radius wherever the shape is large enough to notice:
.rounded-card {
border-radius: 1.5rem;
corner-shape: squircle; /* progressive enhancement; plain radius is the fallback */
}
Use corner-shape: squircle with the radius doubled relative to what a plain rounded corner would use (Luma pairs squircle 1rem where plain radius would be 0.5rem). Nothing on a Luma page has a sharp corner.
Shadows are layered five-stop ramps at very low alpha, one set for light theme and a stronger set for dark:
--shadow-xs: 0 1px 4px #0000001a;
--shadow-sm: 0 1px 3px #00000005, 0 2px 7px #00000008, 0 3px 14px #0000000a,
0 7px 29px #0000000d, 0 20px 80px #0000000f;
--shadow: 0 1.6px 3px #00000005, 0 4.2px 7px #00000008, 0 8px 14px #0000000a,
0 17.5px 29px #0000000d, 0 48px 80px #0000000f;
--shadow-modal: 0 0 0 1px var(--opacity-8), 0 3px 3px #00000008, 0 8px 7px #0000000a,
0 17px 14px #0000000d, 0 35px 29px #0000000f, 0 -4px 4px 0 #0000000a inset;
Note the modal recipe: a 1px ring drawn in the shadow (not a border), then the ramp, then a subtle inset. Floating chrome (menus, sticky bars) uses a near-opaque surface plus backdrop-filter: blur(16px).
Card material:
rgba(255,255,255,.8) fill, 1px solid white border.rgba(255,255,255,.04) fill, 1px border at the same 4% white.Content column is narrow and centered: --max-width: 820px default, 960px wide pages, 1080px extra-wide, with 1rem horizontal padding. Event pages are a two-column split: square cover art left, content right, collapsing to a single column on mobile. Luma pages breathe through spacing, not boxes; sections separate with whitespace and 8%-opacity dividers, not rules and panels.
--transition: all .3s cubic-bezier(.4, 0, .2, 1); /* default */
--fast-transition: all .2s cubic-bezier(.4, 0, .2, 1); /* hovers */
--slow-transition: all .6s cubic-bezier(.4, 0, .2, 1);
--bounce-transition: all .3s cubic-bezier(.54, 1.12, .38, 1.11); /* delight */
Everything that changes state transitions; nothing snaps. The bounce curve (slight overshoot) is the signature: use it for things appearing, toggles, and small celebratory moments. Loading states shimmer (a moving gray gradient), they do not spin. Reserve particles and confetti for genuinely celebratory moments (a successful RSVP), and respect reduced-motion preferences.
The recipe (from .lux-button): squircle radius 1rem, weight 500, generous horizontal padding, transition on all.
| Size | Height | Font | Padding |
|---|---|---|---|
| small | 30px | 14px | 7px 10px |
| medium | 38px | 16px | 10px 14px |
| large | 44px | 16px | 12px 16px |
Variants, and where Luma spends color:
#353535 fill, white text, hover gray-80. Dark theme: white fill, near-black text, hover gray-20. The main CTA on a page ("Register", "Request to Join") is this.Heights 28px small, 36px medium, 42px large (content-box plus 1px border). Surface-colored fill, 1px border in gray-20 (light) or gray-90 (dark), default radius, 16px text. Focus swaps the border to the primary text color; no glow rings.
Squircle 1.5rem, translucent material (above), padding around 0.75rem 1rem for rows and denser lists, more for feature cards. Cards are clickable as a whole ("hoverable actionable"), lifting slightly on hover via border and shadow, not scale.
Menus: near-opaque elevated surface (#fffd light, gray-100 at 80% dark) with blur(16px) behind, 0.5rem radius, hairline border at low opacity. Modals: 1rem squircle radius, the ring-plus-ramp modal shadow, dimmed overlay (#b5b5b599 light, #0009 dark).
Avatars are circles, stacked with slight cutout overlap when showing attendee lists. Pills (tags, filters) are fully rounded, small type (12 to 14px), translucent or pale-wash fills.
Luma's most distinctive engineering-design idea, and the one worth copying in any ecosystem project, is a three-layer theme cascade:
.theme-root / .theme-root.dark owns the global light/dark decision. Every token above flips here..tint-root (.light / .dark) lets any subtree override the scheme locally (a dark event card embedded in a light page)..event-theme applies per-event branding. From one host-picked color, the system generates a full --brand-5 through --brand-90 ramp, each step with -transparent (0%), -translucent (25%), and -thick-translucent (80%) variants, then remaps surfaces onto it: tinted dark pages set the canvas to a deep shade of the brand color, cards to --brand-70/80, buttons and links to legible brand steps. Modifier classes (tinted, pattern, shader, emoji, confetti, floral, high-contrast) layer effects, and a high-contrast mode swaps in adjusted brand values.Host-facing theming (per help.luma.com): 40+ named themes grouped as Minimal, Confetti, Emoji, Pattern, Seasonal, Warp, and Special Effects. Each bundles a display typeface (loaded per-event, for example Degular via Adobe Fonts, layered in front of the system stack for the event title only), a color scheme, light/dark variants, and optional particles. Custom colors are auto-adjusted for contrast.
The transferable lessons:
Drop-in foundation distilled from the extracted values. Class names follow Luma's own convention so live-site inspection maps 1:1:
/* luma-tokens.css: distilled from luma.com production CSS, 2026-08 */
.theme-root {
--font: -apple-system, BlinkMacSystemFont, "Apple Color Emoji", Inter, Roboto,
"Segoe UI", "Helvetica Neue", Arial, "Noto Sans", sans-serif;
--mono-font: "SF Mono", Menlo, Monaco, Consolas, "Courier New", Courier, monospace;
--font-size-xs: .8125rem; --font-size-sm: .875rem; --font-size-md: 1rem;
--font-size-lg: 1.125rem; --font-size-xl: 1.25rem; --font-size-xxl: 1.375rem;
--font-weight-medium: 500; --font-weight-bold: 600;
--title-line-height: 1.2; --default-line-height: 1.5;
--white: #fff; --black: #151515;
--gray-10: #f8f8f8; --gray-20: #ececec; --gray-30: #e0e0e0; --gray-40: #d4d4d4;
--gray-50: #b5b5b5; --gray-60: #959595; --gray-70: #757575; --gray-80: #555;
--gray-90: #353535; --gray-100: #232323;
--cranberry: #f31a7c; --barney: #ab46dd; --purple: #682fff; --blue: #146aeb;
--green: #3cbd2c; --yellow: #d69712; --orange: #f8712b; --red: #ed2b32;
--brand-color: var(--cranberry);
--brand-pale-bg-color: #f31a7c22;
--brand-faint-bg-color: #f31a7c11;
--primary-color: var(--black);
--secondary-color: var(--gray-70);
--tertiary-color: var(--gray-50);
--primary-bg-color: var(--white);
--page-bg-color: var(--gray-10);
--divider-color: rgba(21, 21, 21, .08);
--opacity-8: rgba(21, 21, 21, .08);
--opacity-16: rgba(21, 21, 21, .16);
--card-bg-color: rgba(255, 255, 255, .8);
--card-border-color: #fff;
--primary-button-bg-color: var(--gray-90);
--primary-button-color: var(--white);
--input-border-color: var(--gray-20);
--small-border-radius: .25rem; --border-radius: .5rem;
--large-border-radius: 1rem; --card-border-radius: 1.5rem;
--shadow-xs: 0 1px 4px #0000001a;
--shadow-sm: 0 1px 3px #00000005, 0 2px 7px #00000008, 0 3px 14px #0000000a,
0 7px 29px #0000000d, 0 20px 80px #0000000f;
--shadow-modal: 0 0 0 1px var(--opacity-8), 0 3px 3px #00000008, 0 8px 7px #0000000a,
0 17px 14px #0000000d, 0 35px 29px #0000000f, 0 -4px 4px 0 #0000000a inset;
--backdrop-blur: blur(16px);
--transition: all .3s cubic-bezier(.4, 0, .2, 1);
--fast-transition: all .2s cubic-bezier(.4, 0, .2, 1);
--bounce-transition: all .3s cubic-bezier(.54, 1.12, .38, 1.11);
--max-width: 820px; --max-width-wide-page: 960px; --horizontal-padding: 1rem;
--default-gradient: linear-gradient(-45deg, #8a18a8 0%, #ce2756 51.6%, #e7a90d 100%);
background: var(--page-bg-color);
color: var(--primary-color);
font-family: var(--font);
line-height: var(--default-line-height);
}
.theme-root.dark {
--cranberry: #f98dbe; --barney: #d27cfe; --purple: #b596ff; --blue: #76adff;
--green: #77d86b; --yellow: #f2ca77; --orange: #fba67a; --red: #ff766d;
--primary-color: var(--white);
--secondary-color: var(--gray-40);
--tertiary-color: var(--gray-60);
--primary-bg-color: var(--black);
--page-bg-color: var(--black);
--divider-color: rgba(255, 255, 255, .08);
--opacity-8: rgba(255, 255, 255, .08);
--opacity-16: rgba(255, 255, 255, .16);
--card-bg-color: rgba(255, 255, 255, .04);
--card-border-color: rgba(255, 255, 255, .04);
--primary-button-bg-color: var(--white);
--primary-button-color: var(--black);
--input-border-color: var(--gray-90);
--default-gradient: linear-gradient(-45deg, #d118ff 0%, #f32861 51.6%, #ffbe19 100%);
}
/* sample components in the Luma idiom */
.lux-card {
background: var(--card-bg-color);
border: 1px solid var(--card-border-color);
border-radius: var(--card-border-radius);
corner-shape: squircle;
transition: var(--transition);
}
.lux-card:hover { border-color: var(--opacity-16); box-shadow: var(--shadow-sm); }
.lux-button {
font: var(--font-weight-medium) var(--font-size-md)/1 var(--font);
background: var(--primary-button-bg-color);
color: var(--primary-button-color);
border: 1px solid transparent;
border-radius: var(--large-border-radius);
corner-shape: squircle;
padding: 10px 14px;
cursor: pointer;
transition: var(--transition);
}
.lux-button.light { background: var(--opacity-8); color: var(--secondary-color); }
Any of these instantly breaks belonging next to Luma:
#000 canvases or stark white-on-black with hard corners. Luma's dark is #151515 and everything is rounded.Before shipping, verify against the live site, not memory:
#151515, surfaces white at 4 to 8%, accents at the light ramp steps.Near-black surfaces with translucent white layers instead of fills. The 9:16 rounded player, a 48px circle action rail, a 240px guide. Dark only.
Purpose: make FlyFeed look native to the YouTube Shorts desktop surface without impersonating it. Provenance: every value below was read from the live site with getComputedStyle on youtube.com/shorts/{id} (logged in, dark theme, 1440px viewport) plus a stylesheet census (1,464 custom properties, 25,938 rules). YouTube publishes no design guideline; its own token names are now hashed (--tffc2fd3a644f6275), so the names here are descriptive, not theirs. Re-inspect if the site diverges. No official YouTube docs were used because none exist.
Borrow: the material language (near-black surfaces, translucent white layers, Roboto, the 9:16 rounded player, the 48px circle action rail, the 240px guide, pill buttons). Do not borrow: the YouTube logo, the #FF0033 play-button mark, the Shorts glyph, the wordmark, or any claim of affiliation. FlyFeed uses its own mark and name.
:root {
--bg: #0f0f0f; /* html background, the only true surface */
--bg-raised: #121212; /* search box fill */
--line: #303030; /* 1px borders (search box) */
--text: #f1f1f1; /* primary text and icons */
--text-2: #aaaaaa; /* secondary text */
--text-3: #717171; /* disabled, census rank 3 grey */
--white: #ffffff; /* overlay text ON the video is pure white, not #f1f1f1 */
--layer-05: rgba(255,255,255,0.05);
--layer-10: rgba(255,255,255,0.1); /* action buttons, nav buttons, hover, active guide row */
--layer-20: rgba(255,255,255,0.2); /* pressed */
--scrim-30: rgba(0,0,0,0.3);
--scrim-60: rgba(0,0,0,0.6);
--scrim-80: rgba(0,0,0,0.8);
--blue: #3ea6ff; /* links, call to action */
--red: #ff0033; /* YouTube brand red. Reserved: do NOT use as an accent */
--font: Roboto, Arial, sans-serif; /* no webfont is loaded; system Roboto or Arial fallback */
}
Typography scale actually in use (census): 10, 11, 12, 14, 16, 18, 24px. Weights 400 and 500 only; 700 appears 35 times in 2,500 and should be treated as absent.
| Role | Size / line | Weight | Color |
|---|---|---|---|
| Guide (sidebar) row | 14 / 20 | 400, active 500 | --text |
| Overlay channel handle | 14 / 20 | 400 | --white |
| Overlay title | 14 / 20 | 400 | --white |
| Action rail count label | 12 / 18 | 400 | --text |
| Pill button (Subscribe) | 12 / 32 | 500 | black on white |
| Search input | 16 | 400 | --white |
padding: 0 16px, transparent over --bg. Content starts at y=64 (8px gap).--layer-10 fill.452.25 x 804 at this viewport (height = viewport - 104), radius 12px, overflow: hidden, object-fit: cover, black letterbox. Horizontally centered in the content area, not the viewport.justify-content: space-between, align-items: flex-end, width = player + 72. i.e. [player][12px][48px rail][12px].--layer-10, icon 24px --text, label 12/18 centered beneath. Each button+label block is 78px tall. Rail sits OUTSIDE the video, bottom-aligned to the player. Order on YouTube: like, dislike (hidden here), comments, share, remix, more.linear-gradient(0deg, rgba(0,0,0,.4), rgba(0,0,0,0)), radius 0 0 12px 12px, 76px tall, 16px inset. Stack: [avatar 32px circle, 8px, handle, 8px, Subscribe pill] then [title].--layer-10, 24px icon, pinned right of the content area, vertically centered.--bg-raised, 1px solid --line, padding 0 6px 0 16px.--layer-10 control brightens to --layer-20.12M, 29K, 289).#000 as a page background; the page is #0f0f0f and the video is what is black.#0f0f0f; text is #f1f1f1, not white, except on video.Every file in this index was reverse-engineered from a live site rather than copied from a brand kit. The procedure is the same each time.
Read the site, not a description of the site. Brandfetch entries, Dribbble mockups and Figma Community kits are guesses made by people who do not work there. Every value in these files was read off the served CSS or a getComputedStyle probe on a visible element, and each file records the exact pages sampled and the date.
/design.md, /brand, /style-guide and /llms.txt all 404, and everything comes off the DOM.sans-serif nodes and rgb(0,0,238) links nobody chose.A guideline is only finished when something can be built from it without looking back at the original site. Every example page in this index was built from its file alone, with invented names and content, so the file is doing the work and not the source.
Each file is a dated snapshot and says so at the top: on any conflict between the file and the live site, the live site wins. Re-extract when a site visibly diverges.
Three of the five live in skills/user/<brand>-design/SKILL.md, symlinked into ~/.claude/skills, ~/.cursor/skills and ~/.agents/skills, so any agent can load them by name. Luma and YouTube Shorts are still standalone files.