Skip to content

Design system

This site’s design system

The colours, type, spacing and components this site is built with. Every value on this page is read from the stylesheet when the site is built, and every contrast ratio is computed from those values.

It documents this site only; it is not a sample of client work. The build fails if any text and background pair listed below drops under WCAG 2.2 AA.

Source: src/styles/global.css · tailwindcss/theme.css

Colour

Two scales, brand (blue) and ink (neutral), plus paper for the light page and signal for live status. Values are OKLCH; the hex is the sRGB colour the browser paints.

Under each colour: its contrast as text on the light page (paper) and on the dark page (ink-950).

Brand

  • brand-50

    #eef4ff

    oklch(96.58% 0.0161 262.75)

    1.0:1 on paper
    17.3:1 on ink-950

  • brand-100

    #dbe6ff

    oklch(92.43% 0.0361 266.7)

    1.2:1 on paper
    15.2:1 on ink-950

  • brand-200

    #bfd2ff

    oklch(86.45% 0.0661 267.05)

    1.4:1 on paper
    12.6:1 on ink-950

  • brand-300

    #93b3ff

    oklch(77.27% 0.115 266.28)

    2.0:1 on paper
    9.2:1 on ink-950

  • brand-400

    #6088ff

    oklch(65.68% 0.1817 267.3)

    3.1:1 on paper
    5.8:1 on ink-950

  • brand-500

    #3b63ff

    oklch(57.01% 0.2352 267.14)

    4.5:1 on paper
    4.0:1 on ink-950

  • brand-600

    #2542f5

    oklch(49.99% 0.2626 266.9)

    6.3:1 on paper
    2.8:1 on ink-950

  • brand-700

    #1d33dd

    oklch(45.1% 0.2505 266.82)

    7.8:1 on paper
    2.3:1 on ink-950

  • brand-800

    #1d2cb2

    oklch(39.34% 0.2068 268.16)

    9.8:1 on paper
    1.8:1 on ink-950

  • brand-900

    #1d2c8a

    oklch(35.08% 0.1557 269.17)

    11.3:1 on paper
    1.6:1 on ink-950

  • brand-950

    #121a54

    oklch(25.47% 0.1048 270.77)

    15.5:1 on paper
    1.1:1 on ink-950

Ink

  • ink-50

    #f7f7f8

    oklch(97.64% 0.0013 286.38)

    1.0:1 on paper
    17.8:1 on ink-950

  • ink-100

    #eeeef1

    oklch(95% 0.004 286.32)

    1.1:1 on paper
    16.5:1 on ink-950

  • ink-200

    #d9dade

    oklch(88.88% 0.0055 274.96)

    1.3:1 on paper
    13.7:1 on ink-950

  • ink-300

    #b7b8c0

    oklch(78.42% 0.0113 280.42)

    1.9:1 on paper
    9.6:1 on ink-950

  • ink-400

    #8f909c

    oklch(65.64% 0.0177 282.1)

    3.0:1 on paper
    6.0:1 on ink-950

  • ink-500

    #70717e

    oklch(55.22% 0.0201 282.22)

    4.6:1 on paper
    3.9:1 on ink-950

  • ink-600

    #585966

    oklch(46.78% 0.0209 282.07)

    6.6:1 on paper
    2.7:1 on ink-950

  • ink-700

    #474853

    oklch(40.49% 0.0184 281.42)

    8.7:1 on paper
    2.1:1 on ink-950

  • ink-800

    #2c2d35

    oklch(29.94% 0.0145 279.85)

    13.2:1 on paper
    1.3:1 on ink-950

  • ink-900

    #1a1b22

    oklch(22.42% 0.0137 278.83)

    16.5:1 on paper
    1.1:1 on ink-950

  • ink-950

    #0e0f14

    oklch(16.97% 0.0105 276.32)

    18.4:1 on paper
    1.0:1 on ink-950

Surface and status

  • paper

    #fbfbfa

    oklch(98.78% 0.0013 106.42)

    1.0:1 on paper
    18.4:1 on ink-950

  • signal

    #22c55e

    oklch(72.27% 0.192 149.58)

    2.2:1 on paper
    8.4:1 on ink-950

Contrast

The text and background pairs the components use, in both themes. Transparent backgrounds are blended over the surface below them, as the browser does.

55 of 55 pairs pass AA, and 34 of the text pairs also reach AAA.

Light theme

Light theme
Use Sample Text or element Background Ratio Minimum Result
Body text body ink-800 #2c2d35 paper #fbfbfa 13.2:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Headings h1, h2, h3, h4 ink-950 #0e0f14 paper #fbfbfa 18.4:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Secondary text .section-head .lead ink-600 #585966 paper #fbfbfa 6.6:1 4.5:1 Text · 1.4.3 Pass · AA
Metadata .section-head .meta ink-500 #70717e paper #fbfbfa 4.6:1 4.5:1 Text · 1.4.3 Pass · AA
Links .link-underline brand-700 #1d33dd paper #fbfbfa 7.8:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Links on cards .link-underline brand-700 #1d33dd white #ffffff 8.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Eyebrow labels .eyebrow brand-700 #1d33dd paper #fbfbfa 7.8:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Navigation links .nav-link ink-600 #585966 paper #fbfbfa 6.6:1 4.5:1 Text · 1.4.3 Pass · AA
Chips .chip ink-700 #474853 white #ffffff 9.0:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Brand chips .chip-brand brand-700 #1d33dd brand-50 #eef4ff 7.4:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Core skill chips .chip-core white #ffffff ink-950 #0e0f14 19.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Familiar skill chips .chip-familiar ink-500 #70717e white #ffffff 4.8:1 4.5:1 Text · 1.4.3 Pass · AA
Primary button .btn-primary white #ffffff ink-950 #0e0f14 19.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Primary button, hover .btn-primary white #ffffff ink-800 #2c2d35 13.6:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Brand button .btn-brand white #ffffff brand-600 #2542f5 6.6:1 4.5:1 Text · 1.4.3 Pass · AA
Brand button, hover .btn-brand white #ffffff brand-700 #1d33dd 8.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Ghost button .btn-ghost ink-800 #2c2d35 paper + white/60 #fdfdfd 13.4:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Field text .field ink-900 #1a1b22 white #ffffff 17.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Placeholder .field ink-500 #70717e white #ffffff 4.8:1 4.5:1 Text · 1.4.3 Pass · AA
Field labels .field-label ink-500 #70717e paper #fbfbfa 4.6:1 4.5:1 Text · 1.4.3 Pass · AA
Field errors .field-error rose-700 #c70036 white #ffffff 6.0:1 4.5:1 Text · 1.4.3 Pass · AA
Error summary .error-summary rose-800 #a50036 rose-50 #fff1f2 7.2:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Field border .field ink-400 #8f909c white #ffffff 3.1:1 3:1 Non-text · 1.4.11 Pass · AA
Focus ring :root brand-600 #2542f5 paper #fbfbfa 6.3:1 3:1 Non-text · 1.4.11 Pass · AA
Focus ring on cards :root brand-600 #2542f5 white #ffffff 6.6:1 3:1 Non-text · 1.4.11 Pass · AA

Dark theme

Dark theme
Use Sample Text or element Background Ratio Minimum Result
Body text body ink-100 #eeeef1 ink-950 #0e0f14 16.5:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Headings h1, h2, h3, h4 white #ffffff ink-950 #0e0f14 19.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Secondary text .section-head .lead ink-300 #b7b8c0 ink-950 #0e0f14 9.6:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Metadata .section-head .meta ink-400 #8f909c ink-950 #0e0f14 6.0:1 4.5:1 Text · 1.4.3 Pass · AA
Links .link-underline brand-300 #93b3ff ink-950 #0e0f14 9.2:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Links on cards .link-underline brand-300 #93b3ff ink-950 + ink-900/70 #16171e 8.6:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Eyebrow labels .eyebrow brand-300 #93b3ff ink-950 #0e0f14 9.2:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Navigation links .nav-link ink-300 #b7b8c0 ink-950 #0e0f14 9.6:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Chips .chip ink-200 #d9dade ink-900 #1a1b22 12.2:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Brand chips .chip-brand brand-200 #bfd2ff ink-950 + ink-900/70 + brand-950/40 #141834 11.4:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Core skill chips .chip-core ink-950 #0e0f14 white #ffffff 19.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Familiar skill chips .chip-familiar ink-400 #8f909c ink-950 + ink-900/70 #16171e 5.6:1 4.5:1 Text · 1.4.3 Pass · AA
Primary button .btn-primary ink-950 #0e0f14 white #ffffff 19.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Primary button, hover .btn-primary ink-950 #0e0f14 ink-100 #eeeef1 16.5:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Brand button .btn-brand white #ffffff brand-500 #3b63ff 4.7:1 4.5:1 Text · 1.4.3 Pass · AA
Brand button, hover .btn-brand white #ffffff brand-600 #2542f5 6.6:1 4.5:1 Text · 1.4.3 Pass · AA
Ghost button .btn-ghost ink-100 #eeeef1 ink-950 #0e0f14 16.5:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Field text .field ink-100 #eeeef1 ink-950 + ink-900/70 + ink-950/60 #111218 16.1:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Placeholder .field ink-400 #8f909c ink-950 + ink-900/70 + ink-950/60 #111218 5.9:1 4.5:1 Text · 1.4.3 Pass · AA
Field labels .field-label ink-400 #8f909c ink-950 #0e0f14 6.0:1 4.5:1 Text · 1.4.3 Pass · AA
Field errors .field-error rose-300 #ffa1ad ink-950 #0e0f14 9.9:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Error summary .error-summary rose-200 #ffccd3 ink-950 + rose-500/10 #26111b 12.5:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Field border .field ink-500 #70717e ink-950 + ink-900/70 + ink-950/60 #111218 3.8:1 3:1 Non-text · 1.4.11 Pass · AA
Focus ring .dark brand-300 #93b3ff ink-950 #0e0f14 9.2:1 3:1 Non-text · 1.4.11 Pass · AA
Focus ring on cards .dark brand-300 #93b3ff ink-950 + ink-900/70 #16171e 8.6:1 3:1 Non-text · 1.4.11 Pass · AA
Console text .console ink-100 #eeeef1 ink-950 #0e0f14 16.5:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Console keys .console .k brand-300 #93b3ff ink-950 #0e0f14 9.2:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Console strings .console .s emerald-300 #5ee9b5 ink-950 #0e0f14 12.5:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Console numbers .console .n amber-200 #fee685 ink-950 #0e0f14 15.3:1 4.5:1 Text · 1.4.3 Pass · AA AAA
Console comments .console .c ink-400 #8f909c ink-950 #0e0f14 6.0:1 4.5:1 Text · 1.4.3 Pass · AA

Ratios use the sRGB value of each colour. On wide-gamut screens, Tailwind’s default rose, emerald and amber can look slightly more saturated.

Typography

Bricolage Grotesque for headings and Inter for text, both variable fonts in the latin subset. The fallback faces use metric overrides, so swapping to the web font doesn’t shift the layout.

Families

  • Fast, accessible frontends

    Headings

    --font-display: 'Bricolage Grotesque Variable', 'Bricolage Fallback', ui-sans-serif, system-ui, sans-serif

  • Fast, accessible frontends

    Text and interface

    --font-sans: 'Inter Variable', 'Inter Fallback', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif

  • Fast, accessible frontends

    Labels, code and console

    --font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace

Display sizes

Display sizes are fluid: they grow with the viewport between the minimum and the maximum of the clamp().

  • Token
    text-display-xl
    Size
    clamp(3rem, 8vw, 6.5rem)
    Line height
    0.95
    Letter spacing
    -0.035em
  • Token
    text-display-lg
    Size
    clamp(2rem, 4vw, 3.25rem)
    Line height
    1.02
    Letter spacing
    -0.03em
  • Token
    text-display-md
    Size
    clamp(1.5rem, 2.6vw, 2.125rem)
    Line height
    1.1
    Letter spacing
    -0.02em

Text sizes

  • text-xs · 0.75rem (12 px) Fast, accessible frontends
  • text-sm · 0.875rem (14 px) Fast, accessible frontends
  • text-base · 1rem (16 px) Fast, accessible frontends
  • text-lg · 1.125rem (18 px) Fast, accessible frontends
  • text-xl · 1.25rem (20 px) Fast, accessible frontends
  • text-2xl · 1.5rem (24 px) Fast, accessible frontends

Space, radius and elevation

Tailwind’s spacing scale on a 0.25rem (4 px) unit, one content width, and a few radii and shadows, each with one job.

Spacing

  • 1 · 0.25rem
  • 2 · 0.5rem
  • 3 · 0.75rem
  • 4 · 1rem
  • 5 · 1.25rem
  • 6 · 1.5rem
  • 8 · 2rem
  • 10 · 2.5rem
  • 12 · 3rem
  • 16 · 4rem
  • 20 · 5rem
  • 28 · 7rem

Sections have 5rem of vertical padding on phones and 7rem from 640 px up.

Content width: --container-page: 76rem

Radii

  • Chips

    rounded-md · 0.375rem

  • Form fields

    rounded-xl · 0.75rem

  • Cards

    rounded-2xl · 1rem

  • Panels

    rounded-3xl · 1.5rem

  • Buttons

    rounded-full

Shadows

  • Cards and buttons at rest

    shadow-soft: 0 1px 2px rgb(0 0 0 / 0.04), 0 4px 16px -4px rgb(0 0 0 / 0.06)

  • Cards on hover and the console

    shadow-lift: 0 1px 1px rgb(14 15 20 / 0.04), 0 12px 32px -12px rgb(14 15 20 / 0.18)

  • Brand emphasis

    shadow-glow: 0 0 0 1px rgb(59 99 255 / 0.18), 0 20px 60px -20px rgb(59 99 255 / 0.45)

Motion

Short transitions on one easing curve. When the visitor asks for reduced motion, animations, smooth scrolling and view transitions are switched off.

Easing
--ease-out: cubic-bezier(0.2, 0.7, 0.2, 1)
Animations
  • --animate-fade-up: fadeUp 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both
  • --animate-caret: caret 1s steps(1) infinite
  • --animate-pulse-dot: pulseDot 2s ease-in-out infinite

Components

The shared classes in global.css, rendered live. Press Tab to see the focus ring on each control.

Buttons

Hover darkens the background, pressing scales the button to 98 %, and keyboard focus shows the shared focus ring. There is no disabled style: while a form is sending, its button stays focusable and is marked with aria-disabled.

Markup
<button type="button" class="btn-primary">Primary</button>
<button type="button" class="btn-brand">Brand</button>
<button type="button" class="btn-ghost">Ghost</button>
<button type="button" class="btn-ghost btn-sm">Small</button>
Hover
darker background
Keyboard focus
shared focus ring
Pressed
scales to 98 %
Busy
aria-disabled="true", stays focusable
Dark theme
every pair in the dark contrast table

Chips

Static labels, not controls. Skill depth uses three styles: core (filled), solid and familiar (dashed).

CoreChipFamiliarBrand chipLive status
Markup
<span class="chip-core">Core</span>
<span class="chip">Chip</span>
<span class="chip chip-familiar">Familiar</span>
<span class="chip chip-brand">Brand chip</span>
<span class="chip-live">Live status</span>
Dark theme
every pair in the dark contrast table

Form fields

Visible labels, a border of at least 3:1 and a brand ring on focus. An invalid field gets aria-invalid, a message under it and a line in the summary at the top of the form.

Check 1 field before sending: Company.

Enter the company name.

Markup
<div class="w-full"><label class="field-label" for="ds-email">Email</label>
  <input id="ds-email" class="field" type="email" placeholder="name@company.com" autocomplete="off"></div>
<div class="error-summary w-full">Check 1 field before sending: Company.</div>
<div class="w-full"><label class="field-label" for="ds-company">Company</label>
  <input id="ds-company" class="field" type="text" aria-invalid="true" aria-describedby="ds-company-error" autocomplete="off">
  <p id="ds-company-error" class="field-error">Enter the company name.</p></div>
Keyboard focus
brand border and a 4 px ring at 40 %
Invalid
rose border, message under the field, error summary
Dark theme
every pair in the dark contrast table

Cards and panels

Surfaces for grouped content. card-hover lifts a linked card slightly on hover.

Card

Large radius, hairline border and a soft shadow.

Panel

Larger radius and no shadow.

Markup
<div class="card p-5"><p class="font-semibold text-ink-950 dark:text-white">Card</p>
  <p class="mt-1 text-sm">Large radius, hairline border and a soft shadow.</p></div>
<div class="panel p-5"><p class="font-semibold text-ink-950 dark:text-white">Panel</p>
  <p class="mt-1 text-sm">Larger radius and no shadow.</p></div>
Hover
.card-hover
Dark theme
every pair in the dark contrast table

Console

Decorative code panel, dark in both themes.

Markup
<div class="console w-full" aria-hidden="true">
  <div class="console-bar"><span class="console-dot"></span><span class="console-dot"></span><span class="console-dot"></span><span>resume.json</span></div>
  <pre class="px-4 py-3 overflow-x-auto"><span class="c">// Oscar Arenas</span>
{ <span class="k">"years"</span>: <span class="n">17</span>, <span class="k">"stack"</span>: <span class="s">"React · Next.js"</span> }</pre>
</div>

Focus

Every interactive element shares one focus style: a 2 px outline in --focus-ring (brand-600 in the light theme, brand-300 in the dark theme), 3 px away from the element.

The focus ring, drawn statically

Accessibility statement

Target: WCAG 2.2 level AA, in English and Spanish, in the light and dark themes.

What the site does

  • A skip link to the main content, and header, main and footer landmarks on every page.
  • A visible focus ring on every interactive element, at least 3:1 against its background.
  • Text contrast of at least 4.5:1 in both themes, checked on every build (the tables above).
  • Every page declares its language, and the English and Spanish versions link to each other.
  • Form fields have visible labels. Errors are announced, listed in a summary with links, and focus moves to the first invalid field.
  • Animations, smooth scrolling and view transitions switch off when reduced motion is requested.
  • Fixed banners never cover the element with keyboard focus (WCAG 2.4.11).

Known limitations

  • Smarter matching in the “Role fit” tools downloads about 138 MB once (the model and its runtime) and needs WebAssembly. Without it, keyword matching runs the same tools with less recall.
  • The WebMCP agent tools need a browser flag for now; every tool also works as a regular form on the page.
  • The interactive tools need JavaScript. The resume itself is static HTML, and it is also available as plain text in Markdown.
  • Linked third-party sites (LinkedIn, certificate issuers, the scheduling page) are outside this statement.
  • This is a self-assessment: no external auditor has reviewed the site.

Feedback

If something here doesn’t work with your browser or assistive technology, email oscarenas@gmail.com and tell me the page and what you were trying to do.

Last reviewed: .