Skip to content

Latest commit

 

History

History
212 lines (172 loc) · 7.53 KB

File metadata and controls

212 lines (172 loc) · 7.53 KB
name graphql.org
description The GraphQL Foundation's site — flat, high-contrast, hairline-ruled.
colors
pri-lightest pri-lighter pri-light pri-base pri-dark pri-darker sec-lighter sec-light sec-base sec-dark sec-darker neu-0 neu-50 neu-100 neu-200 neu-300 neu-400 neu-500 neu-600 neu-700 neu-800 neu-900
hsl(319 100% 96%)
hsl(319 100% 90%)
hsl(318 100% 80%)
hsl(319 100% 44.1%)
hsl(319 100% 30%)
hsl(319 100% 20%)
hsl(79 80% 90%)
hsl(79 82% 80%)
hsl(79 90% 65%)
hsl(79 98% 37%)
hsl(79 98% 23%)
hsl(0 0% 100%)
hsl(75 57% 97%)
hsl(75 15% 95%)
hsl(77 14% 90%)
hsl(76 14% 85%)
hsl(77 14% 80%)
hsl(74 14% 70%)
hsl(76 15% 60%)
hsl(76 15% 40%)
hsl(77 14% 20%)
hsl(75 15% 5%)
typography
display headline title body label
fontFamily fontSize lineHeight
Host Grotesk, ui-sans-serif, system-ui, sans-serif
48px
1
fontFamily fontSize
Host Grotesk, ui-sans-serif, system-ui, sans-serif
40px
fontFamily fontSize
Host Grotesk, ui-sans-serif, system-ui, sans-serif
32px
fontFamily fontSize lineHeight
Host Grotesk, ui-sans-serif, system-ui, sans-serif
14px
1.5
fontFamily fontSize lineHeight
Commit Mono, ui-monospace, monospace
14px
1
rounded
none md full
0
6px
9999px
spacing
section-y section-x container
2rem
1rem
120rem
components
button-primary button-primary-hover button-secondary button-tertiary code-block tag
backgroundColor textColor rounded height padding
{colors.neu-900}
{colors.neu-0}
{rounded.none}
56px
0 32px
backgroundColor
{colors.neu-800}
backgroundColor textColor rounded
{colors.neu-100}
{colors.neu-900}
{rounded.none}
backgroundColor textColor rounded
{colors.neu-100}
{colors.neu-900}
{rounded.none}
rounded typography
{rounded.md}
{typography.label}
backgroundColor textColor rounded
{colors.sec-light}
{colors.neu-900}
{rounded.none}

graphql.org design

Overview

This is the specification's own site, run by a foundation. It is a reference, not a product being sold, and it gets cited. Restraint is the brand: flat surfaces, hairline rules, one accent used sparingly, and claims that survive a skeptical reader.

Two families carry everything. Magenta (pri-*) is GraphQL's colour and marks the primary or active thing. Lime (sec-*) is the accent, for confirmation and highlights. Everything else is neu-*.

Colors

Semantic tokens only, defined as HSL triples in src/app/colors.css and exposed through tailwind.config.ts.

The neu-* ramp inverts between light and dark automatically — neu-0 is white in light mode and near-black in dark. So text-neu-900 is the correct foreground in both modes and needs no dark: variant. Reach for dark: only when the semantic token is genuinely wrong for one mode.

pri-* and sec-* do not invert. A magenta surface stays magenta.

Never write a hex value in a component.

Typography

Host Grotesk for text, Commit Mono for code, both loaded as CSS variables in src/app/fonts.

Use the typography-* utilities, never raw text-[NNpx]. Each carries its own responsive step — typography-h2 is 32px on mobile and 48px from md up. That step is the design; a hand-rolled size breaks it. The full scale is d1, h1 through h4, body-lg through body-xs, plus button, menu and tagline.

Layout

.gql-container centres content at a 120rem maximum. .gql-section supplies horizontal and vertical rhythm, stepping up at lg and xl. Compose the two rather than reinventing padding.

Breakpoints are Tailwind's, plus xs at 394px and 3xl at 1920px.

Write one markup tree that reflows. A md:hidden block beside a hidden md:block block is two things to keep in sync and two things to forget.

Elevation & Depth

Depth comes from hairlines and tonal steps, not shadows. A 1px neu-200 border (neu-100 in dark), or adjacent background steps — neu-0 against neu-50. A card grid is gap-px over a neu-200 background so the gaps read as rules.

There is no elevation ladder. Across 408 component files the whole codebase uses about 40 shadows, nearly all decorative glows on marketing surfaces, and none of the design-system primitives use one. There is no sm/md/lg scale to reach for, so don't reach.

Shapes

Square is the default, and it is load-bearing. Buttons, cards, tags and section panels carry no radius. Across 408 component files there are roughly 80 rounded-* usages in total — a rounded corner here is an exception, not a texture.

Three radii are sanctioned:

  • rounded-none — buttons, cards, tags, panels. The default.
  • rounded-md — code blocks, via @/components/pre.
  • rounded-full — status dots, avatars, bullets, and genuine pills.

Anything else is drift. If a new surface wants rounded-lg, the answer is square.

Components

Reach for what exists before writing a new one:

Need Use
Button or link-as-button @/app/conf/_design-system/button
Internal or external link @/app/conf/_design-system/anchor
Section eyebrow _design-system/section-label or @/_design-system/eyebrow
Code block .mdx partial + @/components/pre and nextra/components' Code
Runnable GraphQL @/components/interactive-code-block
Icon **/pixelarticons/*.svg?svgr
Horizontal card row EventsScrollview

Buttons come in primary, secondary and tertiary, at md and lg. They are square, 56px tall (48px at md), and pick their element from the props: href renders an anchor, otherwise a button.

Code blocks are highlighted at build time by Shiki using the themes in src/_design-system/syntax/. That is the only highlighter. Don't add a second one, and don't put dangerouslySetInnerHTML on a <pre>.

Every interactive element gets gql-focus-visible, which applies the shared gql-focus-outline on :focus-visible.

Motion

Motion is decoration and must be optional. Every animation carries a motion-reduce: variant, and anything driven by JavaScript checks prefers-reduced-motion before it starts.

Nothing animates forever. A loop that runs while off screen or in a background tab is a bug; gate it on an IntersectionObserver and visibilitychange.

Never simulate work. If the answer is already known, render it. Artificial delay, fake progress and typewriter effects spend the reader's time to look busy.

New keyframes belong in theme.extend.keyframes, not loose in globals.css.

Do's and Don'ts

Do

  • Use semantic colour tokens and let neu-* invert.
  • Use the typography-* scale.
  • Separate with hairlines and background steps.
  • Give every interactive element a visible focus state.
  • Say what is true. This site gets cited; an uncited number costs more than it earns.

Don't

  • Add a radius or a shadow to a new surface without being asked.
  • Hardcode a hex, a pixel font size, or a breakpoint.
  • Ship a second solution for a problem the repo already solves.
  • Build separate mobile and desktop trees.
  • Animate without a reduced-motion path.