Design systemFeb 15, 2026

A Design System for the Post-Color Era

How this portfolio is built: a monochrome system where hierarchy comes from composition, spacing, and typography—not color.

Introduction

Introduction

Design today is often a race for attention through saturation. For this site I took the opposite approach: by removing the variable of color, I focus entirely on the fundamental pillars of interface design— composition, spacing, and typography.

The result is a strict but flexible system. Every page type—hero, blog, project detail, gallery, contact—uses the same type scale, the same spacing rhythm, and the same component language. In the sections below, each part of the system is explained with the actual UI shown in place.

01 / Typography

Three typefaces carry the whole system. Space Grotesk handles display and headings—tight tracking, bold weight, optional italic for emphasis. Roboto Flex is used for all body copy so reading stays comfortable. JetBrains Mono is reserved for labels, metadata, code, and form controls: it signals “technical” or “structural” without needing color.

RoleCSS variableTailwindUsage
Display / headings--font-headingfont-headingTitles, section headers, nav brand
Body--font-bodyfont-bodyParagraphs, descriptions
Mono / technical--font-monofont-monoLabels, tags, code, metadata, inputs

Sizes are tuned for contrast. Hero type goes very large (7xl–9xl) with tight line-height; section titles sit in the 2xl–4xl range; body stays lg–xl with relaxed leading. Mono text is kept small (10px–13px) and uppercase with wide letter-spacing so it reads as supporting detail.

Space Grotesk

Display & headings

JetBrains Mono

Labels, code & metadata

Type scale in use

HEADLINE / SPACE GROTESK

ABCDEFG

HIJKLMN

BODY / INTER

The quick brown fox jumps over the lazy dog. A minimalist approach to typography ensures clarity and visual impact.

H1 Display72PX / BOLD
H2 Subtitle24PX / BOLD
Body Large18PX / REGULAR
Code Snippet14PX / MONO

Scale in Tailwind

Hero: text-7xl md:text-9xl font-heading font-bold leading-[0.85] tracking-tighter
Page title: text-5xl md:text-7xl font-heading font-bold tracking-tighter uppercase
Section: text-3xl font-heading font-bold tracking-tighter uppercase
Card title: text-2xl font-heading font-bold tracking-tighter
Body: text-lg font-body leading-relaxed
Meta: font-mono text-[10px] opacity-40 uppercase tracking-widest

02 / Color & hierarchy

The palette is monochrome only. There are no accent colors. Everything is built from foreground and background plus opacity steps (e.g. 40%, 60%, 80%). Borders use a subtle border token; strong emphasis uses full foreground.

Light and dark themes swap background and foreground values; the same opacity scale applies in both. Primary actions invert: solid foreground with background text, and hover often adds italic or a slight scale instead of a new color.

Themes are defined in theme.css (:root and .dark). Use opacity steps like foreground/40, foreground/80 for hierarchy.

bg-foreground, text-foregroundFOREGROUND
foreground/80HIGH
foreground/40MEDIUM
foreground/10, bg-mutedMUTED
bg-background, border-borderBACKGROUND / BORDER

03 / Layout & rhythm

Content width is capped at max-w-5xl for the main app and max-w-3xl for long-form (blog, contact). Sections are separated with large vertical gaps (space-y-32, py-20py-32) and horizontal borders (border-b border-border) so the page breathes.

Grids are 2- or 3-column at md and up, with consistent gaps (12–24). Galleries use aspect ratios (e.g. 4/5 for project cards, 21/9 for hero images) so layout stays predictable. A 12-column grid underlies the structure but isn’t visually dominant.

12-column base grid

12-COLUMN GRID — CONSISTENT ACROSS BREAKPOINTS

Key tokens

  • Nav-to-content gap: --content-offset-top-mobile (10rem), --content-offset-top-desktop (14rem). Applied on main as pt-[var(--content-offset-top-mobile)] md:pt-[var(--content-offset-top-desktop)] in layout.
  • Content width: max-w-5xl for main app shell; max-w-3xl for long-form (blog, contact).
  • Section spacing: space-y-32 between major sections; py-20py-32 for vertical rhythm; border-b border-border for section separation.

04 / Components

Buttons, inputs, cards, and badges share the same language: borders, uppercase mono labels, and hover states that use foreground/background inversion or underline. Nothing is rounded; corners stay sharp. Primary buttons are solid foreground with background text and hover:italic; secondary is outlined and inverts on hover. Inputs are underline-only (no filled box) with mono placeholder text.

Buttons

Inputs

I AGREE TO THE TERMS OF SERVICE

Cards & containers

REV_04

INFORMATION ARCHITECTURE

Every element is placed with mathematical precision to ensure optimal user flow and aesthetic balance.

Badges, tags & pills

STABLEEXPERIMENTALBETAARCHIVE
REACTNEXT.JSTYPESCRIPT
AVAILABILITY: OPEN

Snippets

Primary button

React + Tailwind
<button className="px-8 py-3 bg-foreground text-background font-mono text-xs tracking-widest uppercase hover:italic transition-all">
  Primary Action
</button>

Secondary button

React + Tailwind
<button className="px-8 py-3 border border-border font-mono text-xs tracking-widest uppercase hover:bg-foreground hover:text-background transition-all">
  Secondary
</button>

Ghost link

React + Tailwind
<button className="px-4 py-3 flex items-center gap-2 font-mono text-xs tracking-widest uppercase hover:underline underline-offset-4 transition-all">
  Ghost Link <ArrowUpRight size={14} />
</button>

Icon button

React + Tailwind
<button className="p-2 border border-border hover:bg-foreground hover:text-background transition-colors" aria-label="...">
  <Icon size={18} />
</button>

Underline input (with label)

React + Tailwind
<div className="space-y-2">
  <label className="font-mono text-[10px] opacity-40 uppercase">Name</label>
  <input type="text" placeholder="Your name" className="w-full bg-transparent border-b border-border py-3 focus:border-border outline-none transition-colors font-mono text-sm" />
</div>

Bordered card (hover invert)

React + Tailwind
<div className="border border-border p-8 space-y-4 hover:bg-foreground hover:text-background transition-all group">
  <div className="w-12 h-12 border border-border bg-background text-foreground flex items-center justify-center">...</div>
  <h4 className="text-2xl font-heading font-bold tracking-tighter">TITLE</h4>
  <p className="text-sm opacity-60 group-hover:opacity-100 leading-relaxed">Description.</p>
</div>

Badge

React + Tailwind
<span className="px-2 py-0.5 border border-border font-mono text-[9px] tracking-[0.2em] uppercase opacity-60">
  STABLE
</span>

Cards use a single border and optional hover state that inverts foreground/background so the whole block becomes a call-to-action. Badges and tags are small, bordered, mono, and uppercase—used for categories, status, and tech stack.

05 / Page patterns

Reusable layouts for hero, blog, project detail, gallery, and contact. Each uses the same type scale, spacing, and borders.

Hero (landing)

Two-column grid: display headline left, lead + mono pills right. grid md:grid-cols-2 gap-12 items-end. Section: pt-20 pb-32 border-b border-border.

Blog / article

Wrapper max-w-3xl mx-auto space-y-24. Header: meta line (font-mono 10px) + title (5xl–7xl) + subtitle. Sections space-y-12, dividers border-t border-border.

Entity (e.g. project detail)

Back link (mono, uppercase), large italic title (6xl–9xl), meta row with icons. Hero image aspect-[21/9]. Two-column: grid md:grid-cols-3 gap-16, main content md:col-span-2, tags and links on the right.

Gallery

Section header with title + index (mono). Grid md:grid-cols-2 gap-x-12 gap-y-24. Card aspect-[4/5], image group-hover:scale-105 grayscale hover:grayscale-0.

Contact

Title (6xl–8xl italic) + mono subtitle. Two columns: info (Direct, Location, Social) + form. Inputs underline-only; submit primary button (bg-foreground text-background).

06 / Motion

Use motion/react for entrance and hover. No new colors—motion supports hierarchy and feedback.

  • Fade/slide in: initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} with transition.
  • Stagger: delay: index * 0.1, whileInView, viewport={{ once: true }}.
  • Hover: hover:italic, group-hover:scale-105, hover:grayscale-0.
  • Slide-swap text: Two stacked motion.divs—one y: 0 → "-100%", one y: "100%" → 0; use for nav or card titles.
  • Active indicator: motion.div with layoutId="activeTab" so the dot animates between nav tabs.

Checklist for new work

Before shipping a new page or component, confirm:

  • Use only font-heading, font-body, font-mono and the existing scale.
  • Use theme colors (background, foreground, border) and opacity; no new hues.
  • Match section spacing (space-y-32, py-20py-32) and content width (max-w-3xl / max-w-5xl).
  • Buttons and inputs use mono, uppercase, tracking-widest where specified.
  • Cards and galleries use border, aspect ratios, and hover (italic/scale/overlay) as in the system.
  • Nav and footer stay consistent with existing layout and typography.

More documentation and tokens may be added over time.