Noir Minimal Portfolio — Dark-First Design System & Template
A complete, production-ready design system for building calm, text-centric portfolio sites. Noir Minimal Portfolio is dark-first, near-monochrome, and built on Geist typography with muted grays, hairline borders, and a tight radius scale.
Portfolio — September 2026
Features
- Dark-first design system with light mode toggle
- Geist typography, muted grays, and hairline borders
- Working interactive HTML preview and high-res screenshot
- Complete DESIGN.md spec and AGENTS.md AI coding guidelines
- Fully responsive, accessible, and easily customized via CSS variables
Design Specification (DESIGN.md)
Overview
A calm, text-first personal portfolio that reads like a printed index rather than a marketing page. The interface is dark by default, monochrome, and deliberately low-contrast at rest: almost everything sits in a muted gray, and only the thing you are pointing at or tabbing to snaps to full foreground white. Hierarchy is carried by type scale, alignment, and empty space rather than by color, boxes, or shadows. Rows are the primary unit of the design, and every row is a quiet, wide-target link that lifts on hover.
Colors
The palette is a near-monochrome system driven by CSS variables on a dark base. Color is used sparingly and almost exclusively as a hover reward or as a brand accent.
- background: The page canvas, dark by default (
html.dark) with a light counterpart toggled by the same variable set. Everything sits directly on it; there are no raised page-level surfaces. - foreground: The strongest text color. Used for names, the leading clause of the intro paragraph, inline links, and row titles on hover. Reaching this color is the signal that something is active.
- muted-foreground: The default resting color for nearly all text — body copy, row titles, row meta, section labels, and utility toggles. It carries the whole layout.
- border: Hairline outlines used on the avatar, the primary button's focus treatment, and the email input. Borders are thin and never decorative.
- primary / primary-foreground: The single filled element in the design is the
subscribebutton, which uses a solid primary fill with an inverted label. - popover: The surface color for overlays such as the skip link's focus state and tooltips.
- ring: The focus ring color, applied as a 3px ring at 50% opacity across links, buttons, and inputs.
- accent-mastra: The only saturated brand accent in the page, used on hover for the Mastra link.
- accent-marker: A second accent used on hover for social and inline links (X, GitHub, bookmarks, token burn).
- overlay-subtle / overlay-subtle-hover: Translucent foreground washes at 6% and 10% used for keyboard-hint chips and their hover state.
Typography
Typography does all the work in this system. Geist Sans carries everything; Geist Mono is reserved for code-like and numeric surfaces. The scale is small and tight, with muted color doing more differentiation than size.
- lead: The intro paragraph size. Sits at a comfortable reading size and is the largest running text on the page.
- body: Standard body size for form help text and general copy.
- row-title: The name of a project or post. Medium weight, so it reads as a title without needing a larger size.
- row-meta: The right-hand descriptor and the primary button label. Always right-aligned against the title in a
justify-betweenrow. - label: Section headings such as "things i've built", "writing →", and "mailing list". Lowercase, muted, and small — these read as quiet index labels, not headings.
- control: The smallest text, used for the theme and sound toggles and the year column. Tabular numerals for years and dates.
- input: Form input text, which drops to the small size on
mdand above. - mono: Reserved for monospaced contexts implied by the Geist Mono variable; keep it out of prose.
Layout
A single centered column of 42rem (max-w-2xl) with 1.5rem horizontal padding. The page is a full-height flex column with the top padding at 4rem on mobile and 6rem on sm, and 2rem bottom padding, so the content floats in generous vertical space.
The body content is a vertical feed with 4rem gaps between sections, and each section is a stack: a small muted label, then a list. Lists are borderless and stacked with no separators.
Rows use a two-column grid of [3rem_1fr] so the year column stays fixed and the content column aligns identically across every list on the page. Inside each row, a flex justify-between splits the title from the meta line with a 1rem gap. Rows are pulled out of the column with a negative -mx-3 margin and re-padded to 0.75rem so the hover target is wider than the text column while text stays aligned.
Above the page, in the top-right corner, sits a fixed cluster of two utility toggles with a 1rem gap, pinned at 1rem/1.5rem from the top and right edges depending on breakpoint.
Elevation & Depth
There is effectively no elevation. Depth is expressed through color change, not shadow. The only layering cues are:
- A 3px focus ring at 50% opacity that appears on keyboard focus for links, buttons, and inputs.
- Translucent foreground washes (6% and 10%) for keyboard-hint chips.
- A
borderhairline on the avatar, the email input, and the focus state of the primary button. - Tooltips and the skip link use the
popoversurface with aborderoutline.
No box shadows, no blur, no backdrop effects are present in the source.
Shapes
Shapes are soft and small. Radii stay in a tight range: 0.25rem for chips and small keyboard hints, 0.375rem for buttons and inputs, 0.5rem for row hover targets, and a full circle for the avatar and the hand-drawn sketch ring around the name.
The avatar is a 1.75rem circle with a hairline border. The name is wrapped by an animated sketched ellipse drawn with two hand-jittered SVG paths in currentColor, which gives one organic, hand-made gesture to an otherwise geometric layout.
Components
Utility toggles
A fixed top-right pair of text buttons ("dark" and "sound off"), each prefixed by a small keyboard chip showing D and S. Text is small, muted at 60%, and shifts to the standard muted color on hover or focus. The chips use the translucent foreground wash and lighten on hover.
Section label
A small muted line that introduces each list. It can be plain text or a link (the "writing →" heading). Kept lowercase and never bold.
Row link
The core repeating unit: a grid row with an optional year in the first column and a link in the second. The link is a block with rounded corners, negative horizontal margin, and internal padding so the whole row is clickable. At rest, the title is muted; on hover or focus it becomes foreground, and the right-hand meta shifts from 60% muted to full muted. No background change, no underline.
Year / date column
Tabular-nums, small, muted at 60%. Only the first row of a year group shows the year; subsequent rows in the same year leave the cell empty to avoid repetition.
Inline links
Body links (Mastra, Neon, X, GitHub) are foreground-colored and shift to a brand accent on hover — Mastra to its own accent, others to the marker accent. The Neon link is a chip: inline mark plus a label with a brand-specific treatment.
Email input
A 2.25rem tall transparent input with a border outline and 0.375rem radius. Placeholder is muted at 80%. On focus the border softens toward the foreground and the 3px ring appears.
Primary button
The only filled control. A 2.25rem tall solid primary pill-less rectangle with 0.375rem radius and a small medium-weight inverted label. Hover drops the fill to 85% opacity; active scales to 0.97; disabled falls back to a muted surface with muted text.
Badge / chip
Small inline marks such as the Vercel triangle indicator next to shieldcn, sized relative to the surrounding text (0.72em) and carrying a tooltip. The Neon chip pairs a small mark with a semibold label.
Do's and Don'ts
Do
- Keep the page dark-first and near-monochrome; let muted gray carry the layout and reserve full foreground for hover, focus, and active states.
- Align every list to the same
[3rem_1fr]grid so years, titles, and meta lines line up across all sections. - Use the small radius scale (
0.25rem–0.5rem) and keep corners soft but not pill-shaped, except for genuinely circular elements like the avatar. - Signal interactivity with color shift alone — muted to foreground, or muted to brand accent — rather than with background fills or underlines.
- Give rows negative horizontal margin plus internal padding so hover targets are generous while text stays on the column grid.
- Always show a 3px ring at 50% opacity for keyboard focus.
Don't
- Don't introduce box shadows, blurs, or gradient surfaces; depth in this system comes from color and hairlines only.
- Don't add a second filled button or more than one primary action per view.
- Don't uppercase, bold, or enlarge section labels; they are quiet index markers.
- Don't apply saturated color to resting text — accents only appear on hover or focus.
- Don't add card backgrounds, dividers, or borders around list rows.
- Don't widen the content column beyond
42remor increase horizontal padding beyond1.5remon the main container.
designmd.lol