DESIGN.md
Visual system for the Twenty marketing site. Distilled from
packages/twenty-website/src/theme/. Loaded by everyimpeccableinvocation alongside PRODUCT.md.
Light by default. A founder browsing a partner profile in daylight on a 14–27 inch monitor is the default scene. The site does ship a data-scheme="dark" override (see css-variables.ts), but no current public page opts into it. Treat dark as a deferred surface.
Palette is OKLCH-equivalent neutrals at the surface level. The brand accents (blue, pink, yellow, green) are present in the token system but used sparingly — none of them appear on the partner pages.
Tinted neutrals + one accent ≤10%. The accent for partner pages is the deep ink black (var(--color-black-100)) used in CTAs and hover states. Anything beyond a hairline border, an icon glyph, or a primary CTA should question whether it needs color at all.
src/theme/colors.ts + css-variables.ts)Neutrals (the workhorses):
| Token | Hex (computed) | Role |
|---|---|---|
colors.primary.background[100] | #ffffff | Page + card surface |
colors.primary.text[100] | #1c1c1c | Headlines, primary text |
colors.primary.text[80] | #1c1c1ccc | Body text |
colors.primary.text[60] | #1c1c1c99 | Eyebrows, meta, captions |
colors.primary.text[40] | #1c1c1c66 | Disabled / placeholder |
colors.primary.text[20] | #1c1c1c33 | Subtle separators |
colors.primary.text[10] | #1c1c1c1a | Hairline borders |
colors.primary.text[5] | #1c1c1c0d | Subtle fills (rates panel, skill chips) |
colors.primary.border[10] | #1c1c1c1a | Default border |
colors.primary.border[20] | #1c1c1c33 | Hover border |
Reverse palette (for dark CTAs):
| Token | Role |
|---|---|
colors.secondary.background[100] | Filled CTA background (deep ink) |
colors.secondary.text[100] | Filled CTA text (white) |
Brand accents (currently absent from partner pages; available if needed):
colors.accent.blue — #4a38f5 / #8174f8colors.accent.pink — #ed87fc / #f3abfdcolors.accent.yellow — #feffb7 / #feffd9colors.accent.green — #89fc9a / #b0fdbecolors.highlight — same hue as blue accentDo not introduce gradients, glass blurs, or saturated fills on partner pages. Color is conviction here, not decoration.
Three families, each load-balanced via CSS variables:
| Family | Var | Use |
|---|---|---|
theme.font.family.serif | --font-serif | Headlines, partner names, headline values |
theme.font.family.sans | --font-sans | Body, prose, interactive labels |
theme.font.family.mono | --font-mono | Eyebrows, meta, currency labels, tabular numerics |
theme.font.family.retro | --font-retro | Reserved (not used on partner pages) |
Weights: light: 300, regular: 400, medium: 500. No bold. Hierarchy is driven by scale and family contrast, never by weight alone.
Scale (theme.font.size(n) → calc(var(--font-base) * n), where --font-base: 0.25rem ≈ 4px):
letter-spacing: 0.06–0.08em and text-transform: uppercaseBody line length: cap at 65–75ch (the existing PartnerProfileIntro uses max-width: 62ch — keep that order of magnitude).
<h1> at size 9 light reads as a partner's name on the detail page.Base unit 4px. Spacing helper theme.spacing(n) returns n * 4px. Common rhythms on the partner pages:
theme.spacing(10–14) — generous, editorial breathing room.theme.spacing(3–5).theme.spacing(6).theme.spacing(4) mobile, theme.spacing(10) ≥ md breakpoint.theme.radius(n) returns n * 2px. The default card radius is theme.radius(2) = 4px. Pills use 999px. No softer rounding than that.
Borders are hairline (1px solid theme.colors.primary.border[10]). They define edges quietly. On hover they step to border[20]. Never use a chunky border as decoration.
White surface, hairline border, 4px radius, 24px padding, soft shadow on hover only:
background-color: ${theme.colors.primary.background[100]};
border: 1px solid ${theme.colors.primary.border[10]};
border-radius: ${theme.radius(2)};
padding: ${theme.spacing(6)};
&:hover {
border-color: ${theme.colors.primary.border[20]};
box-shadow: 0 12px 32px -16px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
Rounded 999px, 1px border, subtle background fill (primary.text[5] for filter pills, transparent for chip rows), text[80] color, mono or sans font.
Lives in @/design-system/components. Two color modes: primary (deep ink fill, white text) and secondary (transparent fill, ink text + 1px border). variant="contained" is what partner pages use.
PartnerAvatar is a deterministic generated mark from name + slug. Used as fallback when profilePictureUrl is missing. The real photo overlays it at 120px circle on the detail page, 56px on the list card.
PartnerCard: 0.25s ease).0.22, 1, 0.36, 1 (ease-out-quart), 90ms stagger per index.@media (prefers-reduced-motion: reduce) — animations stop, hover translate disabled.@tabler/icons-react, 14–16px on body-level chips, 18–24px on buttons. Always aria-hidden="true" when decorative. Stroke width 2 (default).
outline: 2px solid theme.colors.primary.text[100]; outline-offset: 4px (already used on the card link).aria-label on icon-only buttons, aria-labelledby on sectioned regions.<a target="_blank"> includes rel="noopener noreferrer".In addition to the impeccable shared absolute bans:
0 12px 32px -16px rgba(0,0,0,0.18) — that's the ceiling.