Back to Twenty

Twenty Website — DESIGN.md

DESIGN.md

2.9.07.1 KB
Original Source

Twenty Website — DESIGN.md

Visual system for the Twenty marketing site. Distilled from packages/twenty-website/src/theme/. Loaded by every impeccable invocation alongside PRODUCT.md.

Theme

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.

Color

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.

Strategy: Restrained

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.

Tokens (from src/theme/colors.ts + css-variables.ts)

Neutrals (the workhorses):

TokenHex (computed)Role
colors.primary.background[100]#ffffffPage + card surface
colors.primary.text[100]#1c1c1cHeadlines, primary text
colors.primary.text[80]#1c1c1cccBody text
colors.primary.text[60]#1c1c1c99Eyebrows, meta, captions
colors.primary.text[40]#1c1c1c66Disabled / placeholder
colors.primary.text[20]#1c1c1c33Subtle separators
colors.primary.text[10]#1c1c1c1aHairline borders
colors.primary.text[5]#1c1c1c0dSubtle fills (rates panel, skill chips)
colors.primary.border[10]#1c1c1c1aDefault border
colors.primary.border[20]#1c1c1c33Hover border

Reverse palette (for dark CTAs):

TokenRole
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 / #8174f8
  • colors.accent.pink#ed87fc / #f3abfd
  • colors.accent.yellow#feffb7 / #feffd9
  • colors.accent.green#89fc9a / #b0fdbe
  • colors.highlight — same hue as blue accent

Do not introduce gradients, glass blurs, or saturated fills on partner pages. Color is conviction here, not decoration.

Typography

Three families, each load-balanced via CSS variables:

FamilyVarUse
theme.font.family.serif--font-serifHeadlines, partner names, headline values
theme.font.family.sans--font-sansBody, prose, interactive labels
theme.font.family.mono--font-monoEyebrows, meta, currency labels, tabular numerics
theme.font.family.retro--font-retroReserved (not used on partner pages)

Weight + Size Contrast

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):

  • Display / h1: size 9–12 (36–48px)
  • h2 / section heads: size 7–8 (28–32px)
  • h3 / card heads: size 5–6 (20–24px)
  • Body / prose: size 4–5 (16–20px)
  • Eyebrow / meta: size 3 (12px) with letter-spacing: 0.06–0.08em and text-transform: uppercase

Body line length: cap at 65–75ch (the existing PartnerProfileIntro uses max-width: 62ch — keep that order of magnitude).

Hierarchy contract

  • A serif <h1> at size 9 light reads as a partner's name on the detail page.
  • A mono eyebrow above or below it locates the partner (region · city · country).
  • A serif size 6 light reads as a section head.
  • Body prose is sans regular.
  • Currency values are serif (they read as headline numbers, not stats).
  • Currency labels and meta are mono.

Spacing & Layout

Base unit 4px. Spacing helper theme.spacing(n) returns n * 4px. Common rhythms on the partner pages:

  • Inter-section gap on the detail page: theme.spacing(10–14) — generous, editorial breathing room.
  • Inter-element gap inside a section: theme.spacing(3–5).
  • Card padding: theme.spacing(6).
  • Page horizontal padding: theme.spacing(4) mobile, theme.spacing(10) ≥ md breakpoint.

Radius

theme.radius(n) returns n * 2px. The default card radius is theme.radius(2) = 4px. Pills use 999px. No softer rounding than that.

Borders

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.

Components

Card (PartnerCard, RatesPanel)

White surface, hairline border, 4px radius, 24px padding, soft shadow on hover only:

css
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);
}

Chip / Pill

Rounded 999px, 1px border, subtle background fill (primary.text[5] for filter pills, transparent for chip rows), text[80] color, mono or sans font.

Button / LinkButton

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.

Avatar

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.

Motion

  • Hover transitions: 250ms, ease-out (cubic-bezier curve in PartnerCard: 0.25s ease).
  • Card entrance: 700ms cubic-bezier 0.22, 1, 0.36, 1 (ease-out-quart), 90ms stagger per index.
  • All motion respects @media (prefers-reduced-motion: reduce) — animations stop, hover translate disabled.
  • No bounce, no elastic, no parallax. Editorial restraint.

Iconography

@tabler/icons-react, 14–16px on body-level chips, 18–24px on buttons. Always aria-hidden="true" when decorative. Stroke width 2 (default).

Accessibility Defaults

  • Focus ring: outline: 2px solid theme.colors.primary.text[100]; outline-offset: 4px (already used on the card link).
  • Touch target ≥ 40×40px on mobile.
  • aria-label on icon-only buttons, aria-labelledby on sectioned regions.
  • All <a target="_blank"> includes rel="noopener noreferrer".
  • Color is never the sole carrier of meaning. The money pills carry both an icon and a text label.

Anti-patterns (project-specific)

In addition to the impeccable shared absolute bans:

  • Do not use the brand accent colors (blue/pink/yellow/green) on partner pages unless we have a stronger reason than "to add color".
  • No skeuomorphic shadows on cards. The hover shadow is 0 12px 32px -16px rgba(0,0,0,0.18) — that's the ceiling.
  • No gradients on anything. Including text, borders, and backgrounds.
  • No floating "Trusted by" logo bars on partner pages.