Roman Gomez

Design system · My own work

Portfolio design system

The system behind this site. One set of tokens and components, named the same in Figma and in the code.

Role
Designer, solo
Type
Design system
Tools
Figma, Astro, Claude Code
Year
2026
Some of the parts: tabs, buttons, tags, links and a project card. Every color here is a token.

Overview

One place, not five

This site shows very different work: apps, websites and videos. I wanted it to feel like one place, not five. So I built a small system first. Tokens for color, space and type, plus a set of components, all set up the same way in Figma and in the code.

01 · Structure

Two layers of color

Primitives are the raw values, like neutral/900. Roles are what the rest of the site uses, like color/text/primary. Components only ever use roles, so changing a color is one edit, not a hunt through every page.

Primitivepoints toRoleUsed for
neutral/900#111110color/text/primaryHeadings and body text
neutral/600#52524Ecolor/text/secondaryDescriptions
neutral/25#FAFAF9color/bg/pagePage background
orange/500#DE6E21color/accent/brandCard arrows, the playing outline
orange/700#9A4511color/accent/textActive tab text, section numbers
orange/50#FBEADFcolor/accent/subtleActive tab background, this panel
Each role points at a primitive. Components only know the role.

02 · Decisions

Every token has a reason

I kept a short reason next to every choice, and most of them come down to contrast. If a color can’t pass AA where it’s used, it doesn’t go there. Even when it’s the logo color.

  • View work3.3:1 Fails
    View work18.9:1 Passes

    Orange is a signal, not a button

    White on the logo orange is only 3.3:1. So buttons stay ink, and orange marks things: arrows, the active tab, the playing video.

  • 5.5:1 Passes

    The active tab gets a tint

    Dark orange text on a light orange tint reads at 5.5:1. It still feels on brand without using the logo orange for text.

  • A clearer, easier website for a salon in Redlands

    7.5:1 Passes

    Descriptions are body text

    Card and job descriptions use #52524E at full size, 7.5:1 on the page. They read like content, not a faded caption.

Contrast ratios measured on the real token values. AA needs 4.5:1 for normal text.

03 · Components

Built once, used everywhere

Each component has its states and sizes built in, so a page is just parts snapped together. The home cards, the case study sections and the video grid all come from the same set. Try it: the controls below switch the real Button.

Variant
State
Icon
In code<Button>View work</Button>
This is the same Button used across the site, not a picture of it.

04 · Figma to code

Same names on both sides

Every variable in Figma has a CSS variable with the same name, and every component has a matching file. When I build a page with Claude Code there’s nothing to translate, so the design file and the live site stay in sync.

Tokens
FigmaCode
color/bg/pagevar(--color-bg-page)
color/text/primaryvar(--color-text-primary)
color/accent/brandvar(--color-accent-brand)
space/24var(--space-24)
radius/lgvar(--radius-lg)
Components
FigmaCode
ButtonButton.astro
Project CardProjectCard.astro
Pill NavPillNav.astro
Tab BarTabBar.astro
Case/SectionCaseSection.astro
A few of the matches. The full list lives in tokens.css, with a note to keep it in sync with Figma.

05 · Where it stands

What’s next

The site has a Motion tab now, so motion is next: shared durations and easing as tokens, the same way color works. After that, dark mode. The color layer is already split into primitives and roles, so it’s mostly a second set of values.

More work