The design system implemented across ericcarlisle.com
Overview
This page documents the design system implemented across the site. The CSS custom properties, components, and layout patterns shown here are the source of truth; the page renders and explains what is already in production.
KISS stands for Keep It Stunningly Simple, which is the working rule behind the decisions documented here.
It is intended as a personal reference and may later be shared with interviewers or hiring managers as a portfolio artifact.
The component lab provides an interactive environment for developing and testing components in isolation, with live previews and accessibility testing.
Design Principles
Typography
Headings use var(--font-headers); body copy usesvar(--font-copy); monospace elements usevar(--font-mono).
Visual scale
inline code.Token usage
| Token | Mobile | Desktop | Line height |
|---|---|---|---|
--type-size-h1 | 32 px | 64 px | --line-height-h1 |
--type-size-h2 | 26 px | 45 px | --line-height-h2 |
--type-size-h3 | 22 px | 32 px | --line-height-h3 |
--type-size-h4 | 18 px | 24 px | --line-height-h4 |
--type-size-h5 | 16 px | 19 px | --line-height-h5 |
--type-size-large | 18 px | 22 px | --line-height-body |
--type-size-body | 16 px | 18 px | --line-height-body |
--type-size-small | 13 px | 15 px | --line-height-body |
Content samples
Blockquotes use a left border and slightly larger text. This is how a pull quote or emphasized statement appears.
/* Example token usage */
.card {
padding: var(--space-component);
background: var(--color-card-bg);
border-radius: var(--radius-lg);
}Heading Permalinks
Content-section titles that deserve a stable, shareable URL get a visible chain-link permalink as a sibling after the heading. The pattern is native fragment navigation — an aria-hidden link-chain SVG inside an a.permalink whose href matches the heading's explicit, lowercase kebab-case id, witharia-label="Link to <Visible Title>". No clipboard and no runtime JavaScript.
- Inclusion — add to content-section headings that deserve a stable deep link (e.g. portfolio case-study titles and the “How I work” section). Do not add to article-prose headings or to headings already covered by the article table of contents.
- Stable-ID contract — each heading carries a unique, explicit, lowercase kebab-case
id, never slugified from the visible title at runtime. Treat the IDs as permanent public URLs. - Structure — the permalink is a sibling anchor (never nested inside the heading or another link).
- Accessibility — 44px touch target, visible dashed
:focus-visibleoutline, andscroll-margin-top: calc(var(--header-height, 4.5rem) + var(--space-component))so a fragment lands clear of the fixed header. A restrained:targethighlight marks the landing; underprefers-reduced-motionit becomes a static inset accent bar and permalink transitions are disabled. - Responsive wrapping — the heading and its permalink share one inline text flow, bound by a trailing non-breaking space, so the icon always sits beside the title's final word and never orphaned onto its own row.
- Tokens — uses only existing tokens; no new tokens are introduced.
Canonical implementation: src/pages/portfolio.astro. Live use:/portfolio/#kiss-design-system.
Dark theme
Default / hover / keyboard-focus (Tab to the link) / targeted (choose the permalink): the narrow example shows the icon wrapping with the final title word instead of orphaning.
Light theme
Color Tokens
The default theme is dark. A light theme is applied via[data-theme="light"] or system preference.
Core palette
--bg-mainPage backgroundDark: #0b1020 / Light: #f8fafc--bg-surfaceCards, header, code blocksDark: #111a2e / Light: #ffffff--bg-surface-elevatedElevated surfaces, hover statesDark: #18243a / Light: #e8eef7--text-primaryHeadings, primary textDark: #f8fafc / Light: #0f172a--text-secondaryBody text, descriptionsDark: #cbd5e1 / Light: #334155--text-mutedMeta, captions, labelsDark: #94a3b8 / Light: #475569--brand-primaryLinks, actions, focusDark: #5ea8ff / Light: #005fcc--brand-accentLink hover, success statesDark: #38d9a9 / Light: #007a5a--brand-highlightHighlights, marksDark: #ffb86b / Light: #a14b00--border-mainCard borders, dividersDark: #26344d / Light: #cbd5e1--border-mutedHeader border, subtle separatorsDark: #1a2638 / Light: #e2e8f0--border-controlForm input bordersDark: #52627a / Light: #64748bSemantic roles
| Token | Usage | Source |
|---|---|---|
--color-link | Default links | --brand-primary |
--color-link-hover | Hovered links | --brand-accent |
--color-action | Buttons, CTAs | --brand-primary |
--color-action-hover | Button hover | #3d8ee0 |
--color-focus | Focus outlines | --color-link |
--color-success | Success states | --brand-accent |
--color-card-bg | Card backgrounds | --bg-surface |
--color-card-bg-elevated | Elevated card backgrounds | --bg-surface-elevated |
--color-card-border | Card borders | --border-main |
Spacing Scale
Spacing is derived from the fluid rhythm token var(--rhythm)(≈ 26 px on mobile, ≈ 33 px on desktop).
--space-xscalc(var(--rhythm) * 0.5)13 px → 17 px--space-smcalc(var(--rhythm) * 0.75)20 px → 25 px--space-mdvar(--rhythm)26 px → 33 px--space-lgcalc(var(--rhythm) * 1.5)40 px → 50 px--space-xlcalc(var(--rhythm) * 2)53 px → 66 px--space-sectioncalc(var(--rhythm) * 2.5)66 px → 83 px--space-layoutcalc(var(--rhythm) * 3)79 px → 99 pxLayout Rails
Reading width and page width are intentionally separate. Prose stays narrow for readability, while pages and wide components can use more viewport space.
Components
Navigation
PaginationNav
src/components/PaginationNav.astro
Content
Card
src/components/Card.astro
Designing with tokens
How CSS custom properties shape typography, spacing, color, and layout across the site.
TagFilterBar
src/components/TagFilterBar.astro
Callout
src/styles/components.css
Interaction
Button
src/styles/components.css
ThemeToggle
src/components/ThemeToggle.astro
Integrations
ShareStrip
src/components/ShareStrip.astro
Search UI
src/pages/search.astro, src/styles/pagefind.css
Pagefind injects its markup at runtime; the site restyles it with project tokens. The internals are not rebuilt here.
Contact form fields
src/pages/contact.astro
Content Patterns
Article intro
A large lede paragraph uses var(--type-size-large) to set up an article or section.
Metadata row
Tag list
Accessibility Notes
- Skip link — keyboard users can jump to main content.
- Focus states — visible dashed outline with offset.
- Touch targets — minimum 44px for buttons and icon links.
- Heading permalinks — sibling anchors beside headings with a visible focus ring, accessible labels, and a reduced-motion-safe
:targethighlight. - Color contrast — palettes are chosen for both dark and light modes.
- Semantic HTML — native elements before ARIA.
- Reduced motion — transitions and animations respect user preference.
Known Gaps & Follow-ups
| Item | Note |
|---|---|
| Header height | Measured at runtime by JavaScript; the CSS fallback is 4.5rem. |
| Pagefind search UI | Third-party markup restyled by src/styles/pagefind.css. |
| Portfolio case-study grid | Page-local 1.6fr / 0.8fr sizing in src/pages/portfolio.astro. |
| Homepage role text | One-off letter-spacing on the homepage hero role; not tokenized. |
| Page-specific animations | Scroll-driven header behavior and card entrance motion are scoped to specific pages. |
Implementation Notes
- This page widens the default content rail to
--layout-rail-widewhile keeping prose paragraphs at the readable--layout-rail-prosemeasure. --header-heightis measured and set by JavaScript; the CSS fallback is4.5rem.- Pagefind search UI is third-party markup restyled by
src/styles/pagefind.css. - Some page-specific styles use local sizing not yet captured by shared tokens.
- This page is intentionally omitted from the main navigation and uses
index, followmetadata so search engines can index this living reference. Its placement in the sitemap is governed byscripts/seo-policy.mjs.