Sedge

A living record of the UI decisions behind my digital garden and a playground for shaping a visual language that feels distinctly me.

As a frontend developer with a soft spot for design, I’m drawn to the space between structure and experimentation. This is where those ideas take shape—thoughtful at its core, playful around the edges, and designed to evolve as I do.


Thoughtful + precise
Details with intention
Colorful by design
Expansive on purpose
Flexible foundation
Built to adapt + evolve
Room to play
Experiment, noodle, learn
Delight in the details
Function meets personality

Approach

Intro

A balance of personality and precision.

I like interfaces with personality—the kind that feel expressive and human without losing sight of the details. My design system is a foundation, not a rulebook. It creates enough consistency to hold things together, with room to experiment, try something funky, or break a rule when the experience calls for it.

Four guiding principles

  1. Playful, with purpose

    Moments of delight should feel intentional. Interaction, motion, color, and unexpected details should support the experience rather than compete with it.

  2. Built to customize

    Reusable doesn’t have to mean repetitive. Shared structure keeps things consistent, while flexible color props let components mix, match, and shift with the mood of each page. Lots of choose your own adventure moments!

  3. Systematic underneath

    The fun stuff works because the foundation is solid. Consistent spacing, typography, responsive behavior, and component structure create a reliable foundation to build and experiment on.

  4. Designed to evolve

    Nothing here is too precious to change. Patterns can be questioned, tested, refined, and replaced as I grow, learn, change my mind, and discover new things to try!

Colors

Foundations

These are the colors that make me happy.

Yes, there are a lot of them. I’m making the rules here. I want a whole world of color to pull from as my digital garden grows. Bright, bubbly shades bring the energy, while quieter colors add a little mood. Each comes in soft and bold variations, making it easy to dial things up or down without losing cohesion. Components can mix and match them, leaving plenty of room for pages to develop a personality of their own.

Palette · soft / bold

Pink

soft
#FFC6E6
bold
#F472B6

Red

soft
#FF6358
bold
#D91F1F

Cherry

soft
#AA3857
bold
#6F1B31

Coral

soft
#FFA985
bold
#FE7043

Orange

soft
#FFCB8C
bold
#F28A1B

Clay

soft
#F2BAA2
bold
#D5764D

Marigold

soft
#FFDF90
bold
#F5BF46

Lemon

soft
#FFF89E
bold
#FFE669

Lime

soft
#D3F392
bold
#B0D964

Sage

soft
#ABD3AE
bold
#6EA472

Moss

soft
#B7C98A
bold
#7A8F4E

Mint

soft
#B5FFDB
bold
#5CE1B1

Green

soft
#C4EBD7
bold
#57BC92

Emerald

soft
#479C6D
bold
#2D6144

Teal

soft
#9FE3DF
bold
#2BA3A0

Sky

soft
#B1E3FC
bold
#67CAF8

Blue

soft
#98C7FD
bold
#5088EA

Periwinkle

soft
#D1E3FF
bold
#7F9BE6

Slate

soft
#9DADCB
bold
#5C77AA

Lilac

soft
#D7BAFF
bold
#B985EB

Iris

soft
#B8B5FF
bold
#8B87F5

Plum

soft
#D89FD7
bold
#A855A7

Mauve

soft
#D8B4C4
bold
#9C6B85

Sand

soft
#F4DFC6
bold
#D6B894

Taupe

soft
#C3B6AD
bold
#9D8C81

Cocoa

soft
#A47F68
bold
#644A3D

Text roles

Primary--text-primary
Secondary--text-secondary
Muted--text-muted
Inverse--text-inverse

Ground & ink

Cream — ground--cream · #FEFDFB
Ivory--ivory · #F2E9D8
Charcoal — ink--charcoal · #2A2A2E
Espresso — dark mode ground--espresso · #1D1814

Typography

Foundations

One typeface across the whole system.

After auditioning more fonts than strictly necessary, I landed on Inter, used mostly at regular weight. Hierarchy comes from size and spacing rather than boldness. Big type gets tighter, small labels get airier, and each role has its own token so every page uses type the same way.

Typeface

Inter

ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 &!?@

Used everywhere, from page titles to captions. Monospace only for code and token values.

Weight

Petrichor

Regular · 400The default. Titles, headings, ledes, body.

Petrichor

Medium · 500Nav, small titles, emphasis in UI.

Petrichor

Semibold · 600Spaced-caps labels only.

Petrichor

Bold · 700Reserved. Use sparingly, on purpose.

Scale · by role

Page title.type-title64–140px · 400 · −0.065em · 0.82Bookshelf, Drawing with Code headers

Squirrel

Section heading.type-section36–60px · 400 · −0.055em · 0.88Every Sedge section

Saucy squirrel

Lede.type-lede22–32px · 400 · −0.03em · 1.12Header + section intros

One seriously saucy squirrel, last seen fleeing the bird feeder.

Heading.type-heading22–28px · 400 · −0.025em · 1.15Article sections, years, principles

Seriously saucy squirrel

Subheading.type-subheading17–19px · 500 · −0.015em · 1.3Post subsections (with a marker)

Squirrel sightings

Body.type-body16px · 400 · 1.7Descriptions, article copy

One seriously saucy little squirrel, last seen fleeing the bird feeder. Armed with audacity and possibly an acorn.

Supporting.type-supporting13px · 400 · 1.7 · secondaryPrinciple notes, small explanations

Do not attempt to apprehend — suspect is extremely squirrely and may be carrying snacks.

Label.type-label11.5px · 600 · caps · +0.16emSubheadings
Nut job
Meta.type-meta12px · 400 · 1.35Details rules, captions, counts

Oct 3, 2026 · 5 min read · 18 books

Note.type-note13px · 400 · italic · 1.35Header descriptions, drawer notes

Last seen near the bird feeder, possibly plotting.

Note label.type-note-label13px · 700 · italic · 1.35Contents, Filter by tag, drawer sections

Known accomplices

Code.type-mono13px · mono · 1.6Tokens, code blocks

var(--squirrel-bold) · #8B87F5

Inline code

Property and class names in running text, like border-radius or circle__inner. Automatic inside post bodies; add .inline-code anywhere else.

Tracking · size sets the spacing

Rubber ducky

Tight · −0.065 to −0.025emAnything 22px and up. The bigger, the tighter.

Rubber ducky

Normal · 0Body, supporting, meta.

Rubber ducky

Wide · +0.16emSpaced-caps labels only.

Usage

/* foundations/typography.css — one class per role */
<h1 className="type-title">Bookshelf</h1>
<h2 className="type-section">Colors</h2>
<p className="type-lede">These are the colors that make me happy.</p>
<h3 className="type-heading">Playful, with purpose</h3>
<h4 className="type-subheading">Parent div</h4>
<p className="type-body">Body copy</p>
<p className="type-supporting">A small explanation</p>
<span className="type-label">Usage</span>
<span className="type-meta">Oct 3, 2026 · 5 min read</span>
<span className="type-note-label">Contents</span>
<p className="type-note">A small italic aside</p>
<code className="inline-code">border-radius</code>

Icons

Foundations

Simple, friendly icons from Lucide.

Lucide keeps things consistent without asking me to draw hundreds of tiny pictures. Icons inherit the color around them and use three shared sizes to stay aligned across labels, buttons, and controls.

Sizes

Smallicon-sm · 16px
Mediumicon-md · 20px
Largeicon-lg · 24px
Browse all icons at lucide.dev

In context

Bookshelf
With labelsSmall · beside 14px nav text
5 min read
With metaSmall · beside 12px details
ColorInherits currentColor

Usage

import { BookOpen } from "lucide-react";

{/* Decorative or paired with visible text */}
<BookOpen className="icon-md" aria-hidden="true" />

{/* Icon-only control */}
<button aria-label="Open library">
  <BookOpen className="icon-md" aria-hidden="true" />
</button>

Spacing

Foundations

Quiet spacing makes everything feel intentional.

Five everyday steps (8–32px) handle almost everything inside components. The larger steps are saved for layout, like the top and end of a page, the gap beside a rail, and the air above a subheading.

Scale

--space-14Icon ↔ label, tight inline gaps
--space-28Marker ↔ text, small label gapsEveryday
--space-312Label → content, list rowsEveryday
--space-416Grid gaps, mobile page gutterEveryday
--space-524Desktop page gutter, heading → bodyEveryday
--space-632Lede spacing, below subheadings, drawer paddingEveryday
--space-748Content top, between blocks, min column gap
--space-864Bookshelf content top, mobile page end
--space-980Above subheadings, end of an article
--space-1096Max column gap on wide screens
--space-11128End of the Bookshelf page

Layout aliases

--page-gutter16–24pxLeft and right page padding; grows with the screen
--section-gap48pxVertical space between major page sections
--column-gap48–96pxSticky rail ↔ main column (Bookshelf, posts)

In practice

The vertical rhythm of every section on this page, top to bottom.

  1. Section heading
  2. 32 · space-6Lede
  3. 24 · space-5Description
  4. 80 · space-9Subheading
  5. 32 · space-6Examples

Layout

Foundations

One frame, two shapes.

Every page sits in the same frame, and almost every page uses one of two shapes inside it. The page shell sets the outer width and gutters. Inside, a full-width header comes first, then either a sticky rail beside a main column or a sidebar beside content. Measures keep text at a comfortable reading width.

Page frame

--page-max-width 80rem · shared outer width--page-gutter 16–24px · side padding, grows with the screen

Two shapes

Header + railBookshelf, Drawing with Code. The rail is sticky; --column-gap (48–96px) separates it from the main column. Under 40rem the rail becomes a sticky strip or stacks on top.
Sidebar + contentSedge. Grouped section links on the left, long scrolling content on the right. Under 64rem (1024px) the sidebar becomes a section switcher at the top.

Reading measures

--measure-sm60chShort intros and pattern descriptions
--measure-md65chReading-width page shell, header copy
--measure-lg75chArticle body in the post layout
--measure-xs45chCaptions, narrow columns — not in use yet
--measure-lg · 75ch

This paragraph has one job: be easy to read. Not too wide, not too cramped, and ideally no losing your place halfway through. Turns out even paragraphs like a little breathing room.

--measure-sm · 60ch

This paragraph has one job: be easy to read. Not too wide, not too cramped, and ideally no losing your place halfway through. Turns out even paragraphs like a little breathing room.

Breakpoints

< 40rem640pxThe main switch: rails become a sticky strip, grids stack, headers tighten
Tailwind sm / md / lg640 / 768 / 1024Column counts in one-off grids (covers, swatches)

Shape

Foundations

Square by default, with a soft side.

Shape works in two layers: a small corner-radius scale for the few functional moments that need softening, and a set of custom accent shapes that add character where structure matters less.

Corner radius

--radius-none0The default — page surfaces, covers, swatches, drawer, rules
--radius-xs4Buttons, tooltips
--radius-sm8Code blocks
--radius-md12Focus rings, mobile section switcher
--radius-lg16Callouts, code-art demos
--radius-xl20Post layout on phones
--radius-2xl24Cards
--radius-pill9999Pill buttons, badges, toggles
--radius-card → 2xl · 24px--radius-button → xs · 4px--radius-button-pill → pill--radius-modal → lg · 16px--radius-input → lg · 16px

Accent shapes

Accent shapes are decorative by design — image crops, avatar masks, and background details rather than interactive controls. Their irregularity adds personality without changing how anything is expected to behave.

blob
burst
scallop
arch
clover
flower
circle
ring

Usage

import Shape from "@/components/ui/Shape";

// Decorative only — not a button, not clickable. Color follows
// currentColor, same as lucide-react icons.
<Shape variant="arch" className="h-16 w-16" style={{ color: "var(--lemon-bold)" }} />

Elevation

Foundations

Flat by default. Shadows are for things that float.

On the page, rules, color, and space do the separating, so cards, covers, and buttons sit flat. The drawer is the one surface with a shadow, because it slides over the page. Modals float too, but a dimmed backdrop does that job instead.

On the page

A hairline
Separate with a rule1px border, charcoal at low opacity
A tint
Or with a tintIvory or a soft palette color
A shadow
Not with a shadowResting surfaces don’t lift

When things float

Drawer--shadow-drawer · cast to the left, onto the page
DialogNo shadow · a dimmed page does the lifting

Tokens

--shadow-drawer−24px 0 60px · 12%Side drawers (Reading stats)

Motion

Foundations

Personality in motion.

It shows what changed and makes interactions feel responsive. Fast transitions handle lightweight interactions like hover states. Slower, more expressive motion is saved for larger changes, while shared durations and easing curves keep everything feeling related. When someone prefers less movement, the system quiets itself automatically.

Durations

--motion-duration-fastfast — 200ms
--motion-duration-basebase — 400ms
--motion-duration-slowslow — 600ms

Easings

--motion-ease-standardstandard
--motion-ease-emphasizedemphasized
--motion-ease-deceleratedecelerate
--motion-ease-accelerateaccelerate

Live demo

Durations

Same easing and distance; only the duration changes.

fast — 200ms
base — 400ms
slow — 600ms

Easings

Same duration and distance; only the easing curve changes.

standard
emphasized
decelerate
accelerate

Cursor

The cursor stays a compact dot at rest, expands into a small, hollow ring over interactive elements, and remains a dot over selectable text. Form controls retain their useful native cursors. On dark surfaces, the custom cursor shifts to soft iris to maintain contrast.

Default — 7px dot

Interactive — 16px ring

The transition uses --motion-hover-duration (200ms). Form controls, editable content, disabled controls, and explicitly opted-out regions retain their native cursor:

Usage

<div data-cursor-native>
  {/* Use the native cursor throughout this region. */}
</div>

Accessibility

Foundations

Still learning, and building it in as I go.

I’m building what I learn into Sedge as it grows, so its more playful ideas can work for more people. These foundations are a starting point.

Focus ring

Interactive elements share a consistent .focus-ring utility (foundations/focus.css) so keyboard navigation stays visible throughout the site. Tab the button below to see it in action.

Reduced motion

Motion adds personality to the site, but it shouldn’t be required to use it. When reduced motion is preferred, smooth scrolling and decorative animations are disabled, while motion-token durations collapse to remove unnecessary movement. The animated shelf on the landing page follows the same rule: every piece holds still when reduced motion is on.

Color & Contrast

With such an expansive palette, contrast is something I’m especially mindful of. I’m working on a contrast-checking script to evaluate core color combinations against WCAG AA guidelines, while continuing to review colors in context as they’re introduced throughout the system.

Semantics

I use semantic HTML and native elements wherever possible, with clear heading structure and meaningful labels so the interface makes sense beyond what’s visible on the screen.

Buttons

Components

Soft corners for the everyday, pill shapes for emphasis.

By default, buttons inherit the page accent and use one of three roles, without separate colors for danger or success. Primary and secondary buttons don’t add a pressed style: hover already signals interactivity, and the interface responds immediately after a click, making another visual state unnecessary. Quiet buttons and links have no fill to shift, so they dim briefly when pressed. Every part of a button can also be customized when the default system isn’t the right fit.

Roles

RestHoverFocusPressed
PrimaryHairline outline + accent dot, or an icon in its place. Fills with ink on hover. The default.No pressed state
SecondarySoft accent tint with a bold accent icon.No pressed state
QuietUnderlined text. Low-stakes actions.

Shape

Soft · 4px — the defaultButtons that sit inside content: details bars, drawers, forms, toolbars.
Pill — standalone calls to actionThe main thing to do on a page, alone with room around it. Never mix shapes within one group.

Color and custom tones

By default, buttons take the page’s accent color. Each page sets it once and every button follows, so the same markup can be soft sky and dark cherry on one page, or soft lilac and plum on another. Pages that don’t set one fall back to cherry. For a one-off moment, pass a tone, or full colors if you want complete control. Every color change eases over 250ms rather than snapping.

Soft sky + dark cherry
Soft lilac + plum
Fully custom · teal, inverted on hover

Sizes

sm · 32px · details barsmd · 36px · defaultlg · 44px · calls to actiondisabled

Usage

import Button from "@/components/ui/Button";

// Color comes from the page — no color props needed.
<Button>View all books</Button>                                // primary (default): outline + dot
<Button startIcon={<ChartColumn />}>Reading stats</Button>      // icon replaces the dot
<Button variant="secondary" startIcon={<Copy />}>Copy token</Button>
<Button variant="quiet">Show all</Button>

// A standalone call to action gets the pill.
<Button variant="secondary" shape="pill" size="lg" endIcon={<ArrowRight />}>
  Explore the system
</Button>

// One-off color: the hue's bold for the dot and icons, its soft for
// the secondary background.
<Button tone="pink-bold">Replay all</Button>
<Button variant="secondary" tone="pink">Copy token</Button>

// Full control: palette names or any CSS color. Here bold teal that
// flips to soft teal on hover.
<Button
  variant="secondary"
  tone={{ bg: "teal-bold", text: "teal-soft", icon: "teal-soft", hoverBg: "teal-soft", hoverText: "teal-bold" }}
  startIcon={<Sprout />}
>
  …
</Button>

// Set a page's accent once, on its wrapper:
.my-page {
  --page-accent: var(--cherry-bold);   /* bold fill, icons */
  --page-accent-tint: color-mix(in srgb, var(--cherry-bold) 12%, var(--cream));
  --page-accent-ink: var(--cream);      /* text on the bold fill */
}

Code Blocks

Components

Code blocks match their section color.

One charcoal block with 4px corners and a copy button, used for every snippet on the site. Inside a post section, the code text takes the soft version of that section’s color, so each block matches the heading and markers around it. Outside a section it falls back to iris soft, and a tone prop picks any color by hand. The darkest soft colors (cherry, emerald and cocoa) are lightened automatically so they stay readable. Comments are always cream, whatever the tone, so they stand apart from the code around them.

Follows its section

In a blue section
.circle__outer {
  width: 180px;
  border-radius: 50%;
}
In a yellow section
.circle__outer {
  width: 180px;
  border-radius: 50%;
}
In a pink section
.circle__outer {
  width: 180px;
  border-radius: 50%;
}

Default and override

Outside a section
.circle__outer {
  width: 180px;
  border-radius: 50%;
}
tone="mint"
.circle__outer {
  width: 180px;
  border-radius: 50%;
}

Usage

import CodeBlock from "@/components/ui/CodeBlock";

// Inside a PostSection: text takes the section's soft color
<CodeBlock code={`.circle { border-radius: 50%; }`} />

// Pick a hue yourself. It always uses the soft version.
<CodeBlock code={…} tone="marigold" />

// No copy button
<CodeBlock code={…} copyable={false} />

// Code inside a sentence uses inline code instead
<p>Set <code>border-radius</code> to 50%.</p>

Callouts

Components

Subtle emphasis without interrupting the flow.

I use them in posts for a side note, reminder, related idea, or small update. They stay plain on purpose, with a cream surface, a hairline border, 4px corners, and a short spaced-caps label. The only color is the small marker beside the label, which follows the page accent unless you pass in a tone. Under the hood a callout is an aside, so screen readers know it sits outside the main story. Its label is decorative rather than a heading, it takes an optional action link, and it stays narrow at 40rem so it reads like a note in the margin.

Examples

Usage

import Callout from "@/components/ui/Callout";

<Callout
  eyebrow="Worth a detour"
  actionLabel="Keep wandering"
  actionHref="/garden"
>
  Helpful extra context, without interrupting the main idea.
</Callout>

// The marker follows the page accent. Pass a tone to change it.
<Callout eyebrow="Good to know" tone="green">…</Callout>

Cards

Components

A simple surface for a title and a few lines.

Cards group a short title and supporting copy into one self-contained surface. They look like callouts, with a cream surface, a hairline border, 4px corners, and a spaced-caps title whose marker follows the page accent or a tone you pass in. The difference is what they are. A card is part of the content, one item in a set that sits next to others. Its title is a real heading, so it shows up in the page outline, and the card stretches to fill whatever grid it’s placed in. Callouts are for asides, and cards are for things that belong to the page.

Examples

Color

An expansive, intentional palette for a more expressive web.

Building blocks

Flexible, accessible, and ready to make your own.

Usage

import Card, { CardBody, CardTitle } from "@/components/ui/Card";

<Card>
  <CardTitle>Color</CardTitle>
  <CardBody>An expansive, intentional palette for a more expressive web.</CardBody>
</Card>

// The marker follows the page accent. Pass a tone to change it.
<Card tone="green">…</Card>

Modals

Components

Two dialogs for two jobs.

A small centered modal handles short moments, and a side drawer holds longer content. Both share the same voice, with cream, charcoal text, a hairline edge, the page-accent marker, and a light charcoal backdrop with no shadows. The modal is a notecard with 4px corners, a spaced-caps label, a modest title, and a plain × in the corner. The drawer slides in from the right like a page margin and keeps a big title for content worth settling into. Both are built on the native dialog element, so focus is trapped, Escape closes them, and so does clicking the backdrop.

Which one to use

Modal

import Modal from "@/components/ui/Modal";

const [open, setOpen] = useState(false);

<Button onClick={() => setOpen(true)}>About this page</Button>

<Modal
  open={open}
  onClose={() => setOpen(false)}
  eyebrow="About this page"
  title="Made with CSS"
  tone="green"
  actions={<Button onClick={() => setOpen(false)}>Got it</Button>}
>
  <p>Hover over the knick-knacks for some mini surprises.</p>
</Modal>

Drawer

import Drawer from "@/components/ui/Drawer";

<Drawer
  open={open}
  onClose={() => setOpen(false)}
  eyebrow="Reading stats"
  title="By the numbers"
  tone="green"
>
  <dl className="drawer-list">
    <div><dt>Books</dt><dd>18</dd></div>
  </dl>
</Drawer>
Reading stats

By the numbers

Books
18
Authors
15
Favorites
5
Oldest
1818

Accordion

Components

Answers, revealed as needed.

A single-open FAQ pattern that groups questions without the visual weight of separate cards. It sits directly on the cream page with no container. Hairline rules frame and separate the rows, and a thin plus/minus opens each one. Each question number gets a bold marker and each answer a soft one, the same shape as callout and card labels. They follow the color of the section the accordion sits in, falling back to the page accent, or you can pass in a tone.

Example

A bao bun!

Usage

import Accordion from "@/components/ui/Accordion";

const items = [
  { question: "What's your favorite animal?", answer: "A unicorn!" },
  { question: "What's your favorite food?", answer: "A bao bun!" },
];

// Markers follow the section color: bold for questions, soft for answers
<Accordion items={items} />

// Or pick a hue yourself
<Accordion items={items} tone="pink" />

Badges

Components

Labels for quick context.

Badges are compact labels for topics, categories, and garden metadata. Each pairs a small marker with charcoal text. The marker uses the page accent by default, but can take on any palette color when labels need to be distinguished, like the genre tags on the bookshelf page.

Examples

Field noteLiterary FictionFantasyMade Me CryFavorites

Usage

import Badge from "@/components/ui/Badge";

<Badge>Field note</Badge>
<Badge tone="emerald-bold">Literary Fiction</Badge>

Tooltips

Components

Add context without clutter.

They share the callout and badge anatomy: a hairline border, 4px corners, and a small marker before the label. Plain tooltips are cream with a page-accent marker. Tinted ones use the soft page tint behind the same marker, which on Sedge is soft sky with dark cherry. For anything else, pass your own background, marker, and text colors from the palette.

Examples

Reading goalCounts finished books onlyPlain
The Left Hand of Darkness…The Left Hand of Darkness — Ursula K. Le GuinTinted
FavoritesEvery book I’ve rated five starsCustom tone

Usage

import { CircleHelp } from "lucide-react";
import Tooltip from "@/components/ui/Tooltip";

// Plain (default): cream box, page-accent marker
<Tooltip label="Counts finished books only">
  <span className="label-with-info">
    Reading goal
    <CircleHelp aria-hidden="true" />
  </span>
</Tooltip>

// Tinted: soft page tint + page accent
<Tooltip label="The Left Hand of Darkness — Ursula K. Le Guin" variant="tinted">
  <span className="truncated-title">The Left Hand of Darkness…</span>
</Tooltip>

// Make your own: any palette colors
<Tooltip
  label="Every book I’ve rated five stars"
  tone={{ background: "pink-soft", marker: "emerald-bold", text: "charcoal" }}
>
  <span>Favorites</span>
</Tooltip>

Forms

Components

Forms should feel clear and familiar.

Fields are drawn as ruled lines rather than boxes, like writing on a ruled page; the textarea becomes a few lines of ruled paper. Labels use spaced caps. Focus thickens the line to 2px in the page accent, and checked radios and checkboxes use the same accent. Red is reserved for errors, shown on the line and in a message with the small marker.

Example

Usage

import { FormField, FieldLabel, FieldError } from "@/components/ui/FormField";
import Input from "@/components/ui/Input";
import Select from "@/components/ui/Select";
import Checkbox from "@/components/ui/Checkbox";
import Radio from "@/components/ui/Radio";

<FormField>
  <FieldLabel htmlFor="email">Email</FieldLabel>
  <Input id="email" type="email" placeholder="you@example.com" />
</FormField>

<FormField>
  <FieldLabel htmlFor="site">Your website</FieldLabel>
  <Input id="site" error defaultValue="annapawl" />
  <FieldError>That doesn’t look like a web address yet.</FieldError>
</FormField>

<FormField>
  <FieldLabel htmlFor="role">Role</FieldLabel>
  <Select id="role">
    <option>Designer</option>
    <option>Engineer</option>
  </Select>
</FormField>

<Checkbox id="terms" label="I agree to the terms" />
<Radio name="plan" id="plan-free" label="Free" />

Page Shell

Patterns

Invisible layout scaffolding.

It has no border, radius, or background. It keeps content centered inside a shared maximum width, adds responsive side gutters, and provides equal spacing at the top and bottom of the page.

Inside the page shell

The side gutters adjust with the screen while the content stays centered inside a consistent maximum width.

Usage

import PageShell from "@/components/layout/PageShell";

export default function BooksPage() {
  return (
    <main>
      <PageShell>
        {/* Page content */}
      </PageShell>
    </main>
  );
}

Post Layout

Patterns

The complete structure for long-form posts.

Post Layout brings the header, publishing details, contents rail, reading column, and footer into one repeatable structure. Each section sets one color that flows through its navigation marker, heading, subheadings, and code blocks—change it once and the whole section follows. Every post ends with a Back to top link and can point readers toward what comes next.

CSS Art

An introduction to drawing with code—using HTML elements and CSS properties to turn simple shapes into illustrations in the browser.How I draw little illustrations with nothing but HTML and CSS.

Creative Coding

Drawing with Code

· 10 min readCSS, Illustration, Creative Coding

What is drawing with code?

These shapes are customized by assigning values to properties like height, border-radius, box-shadow, and background-color.

It’s all about the <div>

The <div> element is the building block of every pure CSS composition. A div is simply an empty container used to house other elements and create structure on a web page.

Parent <div>

Every CSS composition starts with a parent div. Think of it as your canvas.

border-radius

This rounds your element’s corners. One value rounds all four, and 50% turns a square into a circle.

Back to top

Navigation comes built in: the Contents list, a color strip on phones, and Back to top at the end. See Navigation for details.

Usage

<PostLayout
  utilityStart={<HomeLogo colors={…} size="1.75rem" />}
  descriptionTitle="CSS Art"
  description="An introduction to drawing with code…"
  shortDescription="How I draw little illustrations with nothing but HTML and CSS."
  eyebrow="Creative Coding"
  title="Drawing with Code"
  publishedDate="Updated Oct 2026"
  readingTime="10 min read"
  postTags="CSS, Illustration, Creative Coding"
  accent="var(--red-bold)"
  next={{ href: "/css-shapes", label: "CSS Shapes" }}
  contents={[
    { id: "what-is-drawing-with-code", label: "What is drawing with code?", tone: "red-bold" },
    { id: "the-div", label: "It’s all about the <div>", tone: "sky-bold" },
    …
  ]}
>
  <PostSection id="the-div" title="It’s all about the <div>">
    <p>Post copy...</p>

    {/* Subsections take the section's color. "label" (default) is a small
        heading with a marker; "code" keeps code headings lowercase + mono. */}
    <PostSubsection title="Parent <div>">
      <p>Every CSS composition starts with a parent div.</p>
    </PostSubsection>
    <PostSubsection title="border-radius" variant="code">
      <p>This rounds your element’s corners.</p>
    </PostSubsection>
  </PostSection>
</PostLayout>