Reference

Design system. Quiet, ruled and fast.

The tokens, type and components behind farhan.app. Every example on this page is rendered with the live stylesheet, and the values are read from it, so this page is always current.

Sections

Principles

A handful of rules decide almost every choice on the site.

  • One accent

    Everything is ink on near-white. Blue appears only for links, focus and the current place.

  • Hairlines, not boxes

    Structure comes from one-pixel rules and space. Cards and containers are the exception.

  • One weight, tight tracking

    Regular weight at every size, with letter-spacing tightening as type grows. Medium is only for emphasis.

  • Atmosphere in panels

    The page stays flat. All colour and depth live in grainy pastel panels.

  • Fluid, not breakpointed

    Type and space scale with the viewport; rows and grids reflow by the width of their container.

  • Accessible by default

    Semantic HTML first, AA contrast, visible focus, keyboard everywhere, reduced motion respected.

Colour

Light only. A near-white ground, charcoal ink at three strengths, hairlines in translucent black and a single blue. Values are shown as they appear in the stylesheet.

Surfaces

  • Page

    --bg-page

    #f8f8f8

    The ground. Every page sits on it.

  • Surface

    --bg-surface

    #fcfcfc

    Raised bands and quiet containers.

  • Elevated

    --bg-elevated

    #ffffff

    Inputs, code blocks, floating controls.

  • Primary

    --bg-primary

    #000000

    The one thing you want clicked.

Ink

  • Aa

    Ink

    --text-primary

    #191919

    16.6:1 on page · AA

    Headings, titles, interface text.

  • Aa

    Ink 78%

    --text-secondary

    #191919 · 78%

    8.3:1 on page · AA

    Body copy and descriptions.

  • Aa

    Ink 64%

    --text-tertiary

    #191919 · 64%

    5.1:1 on page · AA

    Meta, captions and dates. The lightest text that still passes AA.

  • Aa

    Lead 50%

    --lead

    #191919 · 50%

    3.3:1 on page · AA large text

    The quiet first line of a two-tone heading. Large text only.

Accent and status

  • Accent

    --accent

    #317cff

    Focus rings and active states. Never body text.

  • Link

    --link

    #1a5fe0

    Inline links and the current page in the menu. AA on the page.

  • Success

    --success

    #16a34a

    Tip callouts and positive status.

  • Warning

    --warning

    #ea7814

    Warning callouts and the "Required" badge.

  • Danger

    --danger

    #dc2626

    Danger callouts and errors.

Lines and fills

  • Border

    --border

    #000000 · 8%

    Controls, cards and the main hairlines.

  • Border subtle

    --border-subtle

    #000000 · 6%

    Row dividers and section rules.

  • Fill 4%

    --fill

    #000000 · 4%

    Hover and current backgrounds.

  • Fill 6%

    --fill-2

    #000000 · 6%

    Inline code and pressed states.

Tints

  • Tint blue

    --tint-blue

    #337df4 · 10%

    Note callouts, "Recommended" badge.

  • Tint green

    --tint-green

    #16a34a · 10%

    Tip callouts.

  • Tint orange

    --tint-orange

    #f5821e · 14%

    Warning callouts.

  • Tint red

    --tint-red

    #dc2626 · 9%

    Danger callouts.

Gradients

  • Sky

    --panel-sky

    Panel gradient with grain overlay.

  • Haze

    --panel-haze

    Panel gradient with grain overlay.

  • Mist

    --panel-mist

    Panel gradient with grain overlay.

Typography

Three self-hosted families. Geist carries the voice, Inter handles small interface text, and Geist Mono labels things that are data.

  • Geist

    Headings, body and navigation. Weights 400 and 500.

    --font-sans

  • Inter

    Descriptions, captions and small labels.

    --font-ui

  • Geist Mono

    Dates, kickers, numbers and code.

    --font-mono

Scale

  • Display

    36 → 64 px · line 1.04 · tracking −3%

    Page titles and the home headline.

    Data, models and web apps

  • Heading

    28 → 40 px · line 1.08 · tracking −3%

    Post titles and section titles.

    Notes on finance and self-hosting

  • Title

    24 → 32 px · line 1.18 · tracking −2%

    Headings inside long-form content.

    Why locks beat retries

  • Lede

    18 → 22 px · line 1.4 · tracking −1% · ink 78%

    The standfirst under a page title.

    A short sentence that tells you what this page is for.

  • Body

    16 px · line 1.65 · ink 80% · max 42rem

    Reading text.

    Most of my work sits in the gap between a finance team’s question and the system that could answer it.

  • Small

    13 px · Inter · line 1.55 · ink 78%

    Section descriptions and helper text.

    Notes on finance, Excel and self-hosting.

  • Meta

    11 px · mono · uppercase · tracking +4% · ink 64%

    Dates, kickers and labels.

    5 October 2026 · Technical

Two-tone headings

A muted first line followed by a confident statement. The first line is ink at 50%, which is why it is reserved for large type.

Analyst and developer.I build the thing that answers the question.

Spacing and layout

Space is fluid: every value is a clamp() that grows with the viewport between a floor and a ceiling.

Spacing tokens
TokenRangePreview at maximumUse
--gutter 18 → 64 px Page gutter: the side padding of every frame.
--space-hero-t 64 → 120 px Hero, top.
--space-hero-b 56 → 96 px Hero, bottom.
--space-head-t 40 → 72 px Page header, top.
--space-head-b 28 → 48 px Page header, bottom.
--space-sec-t 40 → 60 px Section, top.
--space-sec-b 56 → 90 px Section, bottom.
--space-body-t 28 → 48 px Body content, top.

Frame and widths

Frame
80rem (1280px) with dashed side rules
Header
3.25rem (52px), sticky
Article column
42rem, centred
Docs column
44rem, with sidebar and outline
Menu breakpoint
45rem
Docs layout
3 columns, 2 below 75rem, 1 below 56rem

Rows and grids reflow by container width, not screen width: rows change layout at 40rem and 45rem of their own width, and card grids use auto-fit columns with a minimum size.

Shape, depth and motion

Marketing surfaces stay flat. Rounding and depth are small and reserved for things you interact with.

Radii

  • --r-xs

    4 px

  • --r-sm

    6 px

  • --r-md

    12 px

  • --r-lg

    14 px

  • --r-xl

    16 px

  • --r-full

    pill

Elevation

  • --shadow-raised

    Hovered cards.

  • --shadow-float

    Floating controls.

Motion

Fast
.15s
Link and fill changes.
Base
.2s
Buttons, borders, transforms.
Easing
cubic-bezier(.2,0,0,1)
Quick out, no bounce.
Marquee
40s linear
Pauses on hover.

Colour and fill shifts, a few pixels of travel, and nothing else. When a visitor prefers reduced motion, animation and smooth scrolling are switched off.

Actions

Actions are pills. One black primary per view, a hairline outline for the alternative, and a ghost for everything else.

Buttons

Primary, outline and ghost, each in a default (42px) and small (28px) size. The arrow marks a link that leaves the site.

html
<div class="acts" style="margin:0">
  <a class="btn primary" href="#" data-demo>Primary</a>
  <a class="btn outline" href="#" data-demo>Outline</a>
  <a class="btn ghost" href="#" data-demo>Ghost</a>
  <a class="btn primary" href="#" data-demo>Visit project <svg aria-hidden="true" focusable="false"><use href="#out" /></svg></a>
</div>
<div class="acts" style="margin-top:.75rem">
  <button class="btn sm primary" type="button">Small</button>
  <button class="btn sm outline" type="button">Small outline</button>
  <button class="btn sm ghost" type="button">Small ghost</button>
</div>

Lists and data

Information sits directly on the page. Rows are separated by hairlines, not boxes, and reflow by the width of their own container rather than the screen.

Rows

The standard list: date, title, tag, chevron. External rows rotate the chevron into an arrow. Under 40rem of container width the date and tag move onto their own lines.

html
<ul class="rows" role="list">
  <li><a class="rowline changelog" href="#" data-demo><time class="d" datetime="2026-10">Oct 2026</time><span class="t">I built my first SaaS. Then the client bought it.</span><span class="c">SaaS</span><svg class="ic" aria-hidden="true" focusable="false"><use href="#cr" /></svg></a></li>
  <li><a class="rowline changelog" href="#" data-demo><time class="d" datetime="2026-05">May 2026</time><span class="t">Docker cleanup commands to free up disk space on a VPS</span><span class="c">VPS</span><svg class="ic" aria-hidden="true" focusable="false"><use href="#cr" /></svg></a></li>
  <li><a class="rowline changelog ext" href="#" data-demo><time class="d" datetime="2026-01">Jan 2026</time><span class="t">CSS design system generator</span><span class="c">Web app</span><svg class="ic" aria-hidden="true" focusable="false"><use href="#cr" /></svg><span class="vh">(external site, opens in a new tab)</span></a></li>
</ul>

Resource rows

Name, what it is for, a tag and the external arrow.

html
<ul class="rows" role="list">
  <li><a class="rowline res ext" href="#" data-demo><span class="n">Claude</span><span class="w">The best AI tool for writing high quality code.</span><span class="c">AI · Referral</span><svg class="ic" aria-hidden="true" focusable="false"><use href="#cr" /></svg><span class="vh">(external site, opens in a new tab)</span></a></li>
  <li><a class="rowline res ext" href="#" data-demo><span class="n">Revolut</span><span class="w">My go-to banking app for travel and subscriptions.</span><span class="c">Banking · Referral</span><svg class="ic" aria-hidden="true" focusable="false"><use href="#cr" /></svg><span class="vh">(external site, opens in a new tab)</span></a></li>
</ul>

Facts and stats

A definition list under a hairline. Mono labels over plain values; a stat swaps in a large figure above its caption.

Year
2026
Role
Developer
Built with
JavaScript, Leaflet
Routes mapped
700+
Stops indexed
19k
Initial render
<1s
html
<dl class="facts" style="margin-bottom:1.5rem">
  <div><dt>Year</dt><dd>2026</dd></div>
  <div><dt>Role</dt><dd>Developer</dd></div>
  <div><dt>Built with</dt><dd>JavaScript, Leaflet</dd></div>
</dl>
<dl class="facts" style="margin-bottom:0">
  <div><dt>Routes mapped</dt><dd class="kpi">700+</dd></div>
  <div><dt>Stops indexed</dt><dd class="kpi">19k</dd></div>
  <div><dt>Initial render</dt><dd class="kpi">&lt;1s</dd></div>
</dl>

Tables

Ruled, not boxed. Muted column headers over a single hairline, tabular figures, numbers right-aligned. The wrapper scrolls on narrow screens and is keyboard-focusable.

Illustrative quarterly revenue
PeriodBase rateUpliftRevenue
Q1£118.000.0%£1.42m
Q3£118.003.2%£1.48m
html
<div class="tw" tabindex="0">
  <table class="table">
    <caption class="vh">Illustrative quarterly revenue</caption>
    <thead><tr><th scope="col">Period</th><th scope="col" class="num">Base rate</th><th scope="col" class="num">Uplift</th><th scope="col" class="num">Revenue</th></tr></thead>
    <tbody>
      <tr><th scope="row">Q1</th><td class="num">£118.00</td><td class="num">0.0%</td><td class="num">£1.42m</td></tr>
      <tr><th scope="row">Q3</th><td class="num">£118.00</td><td class="num">3.2%</td><td class="num">£1.48m</td></tr>
    </tbody>
  </table>
</div>

Cards and surfaces

Atmosphere lives in grainy pastel panels. Cards are a panel (or a real image) above a mono meta line, a title and a short description.

Panels

Three gradient panels, each with a fine grain overlay. They cycle across cards and stand in when a project has no thumbnail.

html
<div style="display:grid;gap:.75rem;grid-template-columns:repeat(auto-fit,minmax(min(100%,10rem),1fr))">
  <div class="panel" style="aspect-ratio:16/10;border-radius:var(--r-lg)" role="img" aria-label="Sky panel"></div>
  <div class="panel haze" style="aspect-ratio:16/10;border-radius:var(--r-lg)" role="img" aria-label="Haze panel"></div>
  <div class="panel mist" style="aspect-ratio:16/10;border-radius:var(--r-lg)" role="img" aria-label="Mist panel"></div>
</div>

Card

The whole card is one link; the title turns accent blue on hover or focus.

html
<div style="max-width:24rem">
  <a class="acard" href="#" data-demo>
    <span class="panel haze" aria-hidden="true"></span>
    <p class="meta"><time datetime="2026-04">Apr 2026</time><span aria-hidden="true"> · </span>API</p>
    <h3>London buses</h3>
    <p>TfL's public network data as an interactive map, with first paint under a second.</p>
  </a>
</div>

Long-form content

Articles are one narrow column (42rem) with ink at 80%, generous leading and headings that tighten as they grow. WordPress HTML is styled by the same rules, so a post needs no special markup.

Prose

Headings, paragraphs, links, lists with small ink bullets, ordered steps with mono numerals, and inline code.

Why it matters

You end up screening on the wrong signals. Outcomes beat duties, and a good title carries information. Use docker system df to look first.

  • The system or process you own
  • What changed because you were there
  1. Open Settings and choose Security.
  2. Paste your metadata URL.
html
<div class="prose">
  <h3>Why it matters</h3>
  <p>You end up screening on the wrong signals. <strong>Outcomes</strong> beat duties, and <a href="#" data-demo>a good title</a> carries information. Use <code>docker system df</code> to look first.</p>
  <ul><li>The system or process you own</li><li>What changed because you were there</li></ul>
  <ol><li>Open <strong>Settings</strong> and choose Security.</li><li>Paste your metadata URL.</li></ol>
</div>

Quote and pull quote

A block quote has a hairline rail. The pull quote is a two-tone display line.

If the title tells a hiring manager nothing, the CV has to do all the work.

The queue, not the model,decides what lands.

html
<div class="prose">
  <blockquote>If the title tells a hiring manager nothing, the CV has to do all the work.</blockquote>
  <p class="pull"><span style="color:rgb(var(--lead))">The queue, not the model,</span><span style="display:block">decides what lands.</span></p>
</div>

Callouts

Four tinted callouts with an icon. A tint carries the meaning; the icon and a bold lead word repeat it, so colour is never the only signal.

html
<div style="display:grid;gap:.625rem">
  <aside class="callout note" role="note"><svg aria-hidden="true" focusable="false"><use href="#info" /></svg><div><p><strong>Note:</strong> SAML requires an enterprise plan.</p></div></aside>
  <aside class="callout tip" role="note"><svg aria-hidden="true" focusable="false"><use href="#check" /></svg><div><p><strong>Tip:</strong> Test in a staging organisation before enforcing.</p></div></aside>
  <aside class="callout warn" role="note"><svg aria-hidden="true" focusable="false"><use href="#warn" /></svg><div><p><strong>Warning:</strong> Existing sessions will be signed out.</p></div></aside>
  <aside class="callout danger" role="note"><svg aria-hidden="true" focusable="false"><use href="#warn" /></svg><div><p><strong>Danger:</strong> Disabling SSO removes all linked accounts.</p></div></aside>
</div>

Code

A bordered block with a header strip holding the language and a copy button. The button sits outside the scrolling area, so it never moves while long lines scroll.

bash
# stopped containers, unused networks, dangling images
docker system prune -f

# build cache only: usually the biggest single win
docker builder prune -af
html
<div class="cb">
  <div class="cb-h"><span>bash</span><button class="copyb" type="button" data-copy-self><svg aria-hidden="true" focusable="false"><use href="#copy" /></svg><span>Copy</span></button></div>
  <pre tabindex="0"><code><span class="tk-c"># stopped containers, unused networks, dangling images</span>
docker system prune <span class="tk-f">-f</span>

<span class="tk-c"># build cache only: usually the biggest single win</span>
docker builder prune <span class="tk-f">-af</span></code></pre>
</div>

Forms and filters

Hairline controls on a white surface. Focus is the one moment the accent appears at full strength: a blue outline with a soft halo.

Filter chips and badges

Filter chips are outlined pills that fill black when selected or current. Badges are quiet tinted labels; the tint says what kind.

Neutral Recommended Success Required Danger
html
<ul class="chips" role="list" aria-label="Example filters" style="margin-bottom:1rem">
  <li><a class="fchip" href="#" data-demo aria-current="page">All</a></li>
  <li><a class="fchip" href="#" data-demo>Technical</a></li>
  <li><a class="fchip" href="#" data-demo>Guides</a></li>
</ul>
<div class="chips">
  <span class="chip">Neutral</span>
  <span class="chip blue">Recommended</span>
  <span class="chip green">Success</span>
  <span class="chip orange">Required</span>
  <span class="chip red">Danger</span>
</div>

Empty state

Centred, short and helpful, with a way out.

Nothing matches that

Try a different word.

html
<div class="empty">
  <h3>Nothing matches that</h3>
  <p>Try a different word.</p>
  <button type="button" class="btn sm outline" style="margin-top:.375rem">Clear search</button>
</div>

Marquee

A slow, hover-to-pause scroll of names or logos, softened at both edges. It is hidden from assistive technology (the same names exist once as a plain list) and stops moving for anyone who prefers reduced motion.

Name marquee

Forty seconds per loop, linear. Two identical sets make the loop seamless.

html
<div class="marquee" aria-hidden="true">
  <div class="marq-track">
    <div class="marq-set"><span class="logo-ph">Stagecoach London</span><span class="logo-ph">Shell Energy</span><span class="logo-ph">SEGA</span><span class="logo-ph">MiQ</span></div>
    <div class="marq-set" data-copy="1"><span class="logo-ph">Stagecoach London</span><span class="logo-ph">Shell Energy</span><span class="logo-ph">SEGA</span><span class="logo-ph">MiQ</span></div>
  </div>
</div>

Accessibility

Accessibility is part of the system, not a layer on top of it.

  • Contrast

    All text meets WCAG AA. The lightest ink (64%) is still above 4.5:1 on the page, and the 50% lead colour is only used for large type.

  • Focus

    A 2px accent outline on everything interactive, plus the field halo on inputs. Focus is never removed.

  • Semantics

    Real landmarks, one H1 per page, an unbroken heading order, native buttons and links, and ARIA only where HTML cannot say it.

  • Keyboard

    Skip link, a modal menu that traps focus and closes on Escape, and scrollable regions that can be focused.

  • Motion

    Reduced-motion preferences switch off animation and smooth scrolling, and the marquee stops.

  • Meaning beyond colour

    Callouts pair a tint with an icon and a lead word; the current page is shown by shape, not only hue.