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
| 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
| 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.
Chips
Static labels, not controls. Skill depth uses three styles: core (filled), solid and familiar (dashed).
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.
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
Links
Brand links underline on hover; quiet links are for navigation, where the context already says they are links.
Markup
<a class="link-underline" href="#links">Brand link</a>
<a class="link-quiet" href="#links">Quiet link</a> - Keyboard focus
- shared focus ring
- 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.
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: .