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
01
Playful, with purpose
Moments of delight should feel intentional. Interaction, motion, color, and unexpected details should support the experience rather than compete with it.
02
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!
03
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.
04
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!
Logo
Intro
A persistent route home from anywhere in the garden.
Its silhouette, placement, and motion stay consistent, while its resting and hover colors can adapt to the personality of each page.
States
Default
Hover
Across the garden
The clover silhouette always stays the same, but the petals are totally customizable. Each page can dress the logo in colors that match its own palette and vibe.
Conventions
The complete mark always links to the home page.
Keep its silhouette, size, placement, and motion consistent.
Use page-specific colors with enough contrast against the surface.
The hover flourish is removed automatically for reduced motion.
Usage
import HomeLogo from "@/components/ui/HomeLogo";
<HomeLogo
colors={{
base: "var(--cherry-bold)",
hoverAccent: "var(--moss-soft)",
}}
/>
{/* Optional: give individual petals their own resting colors. */}
<HomeLogo
colors={{
northeast: "var(--periwinkle-soft)",
southeast: "var(--moss-soft)",
southwest: "var(--cherry-soft)",
northwest: "var(--lemon-bold)",
hoverAccent: "var(--iris-bold)",
}}
/>
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 #FFC6E6bold #F472B6
Red
soft #FF6358bold #D91F1F
Cherry
soft #AA3857bold #6F1B31
Coral
soft #FFA985bold #FE7043
Orange
soft #FFCB8Cbold #F28A1B
Clay
soft #F2BAA2bold #D5764D
Marigold
soft #FFDF90bold #F5BF46
Lemon
soft #FFF89Ebold #FFE669
Lime
soft #D3F392bold #B0D964
Sage
soft #ABD3AEbold #6EA472
Moss
soft #B7C98Abold #7A8F4E
Mint
soft #B5FFDBbold #5CE1B1
Green
soft #C4EBD7bold #57BC92
Emerald
soft #479C6Dbold #2D6144
Teal
soft #9FE3DFbold #2BA3A0
Sky
soft #B1E3FCbold #67CAF8
Blue
soft #98C7FDbold #5088EA
Periwinkle
soft #D1E3FFbold #7F9BE6
Slate
soft #9DADCBbold #5C77AA
Lilac
soft #D7BAFFbold #B985EB
Iris
soft #B8B5FFbold #8B87F5
Plum
soft #D89FD7bold #A855A7
Mauve
soft #D8B4C4bold #9C6B85
Sand
soft #F4DFC6bold #D6B894
Taupe
soft #C3B6ADbold #9D8C81
Cocoa
soft #A47F68bold #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.
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.
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.
--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.
Section heading
32 · space-6Lede
24 · space-5Description
80 · space-9Subheading
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
--page-max-width 80rem · shared outer width--page-gutter 16–24px · side padding, grows with the screen
Two shapes
HeaderRailMain column
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.
Top barSidebarSections
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-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.
--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.
blobburstscalloparchcloverflowercirclering
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
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 */
}
Links
Components
The underline carries the color.
Links keep the voice of the text around them. Link text stays charcoal and regular weight, and a 1.5px underline carries the color, thickening on hover. Quieter links use a dotted line that turns solid. Semantic anchors, visible focus, visited states, and external-link announcements keep the pattern useful beyond its appearance. It remains opt-in so navigation and other complex links can keep their own behavior.
Default
I love looking through other people’s bookshelves — mine has book club picks, finds from the Little Library, and a few too many fantasy trilogies.
Covers and page counts come from Google Books (opens in a new tab). Small, low-stakes links — meta and captions — use a dotted line that goes solid on hover.
Links follow their post section’s color, or the page accent outside a post. Pass a tone when a section has its own color, like sky, plum or marigold.
Usage
// TextLink: internal links use next/link; http(s) links open in a// new tab and announce it
<TextLink href="/books">book club picks</TextLink>
<TextLink href="https://getbem.com/naming/" tone="sky-bold">BEM</TextLink>
<TextLink href="https://books.google.com" variant="quiet">Google Books</TextLink>
// Standalone, with an arrow (classes directly)
<a className="text-link text-link--standalone focus-ring" href="/books">
Browse all books
<ArrowRight className="text-link-icon" aria-hidden="true" />
</a>
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.
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 for short moments: a note, a confirmation, a small form. A few lines and one or two actions.
Drawer for longer, browsable content that benefits from keeping the page in view, like the bookshelf’s reading stats.
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>
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 unicorn!
A bao bun!
Saturday!
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
That doesn’t look like a web address yet.
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.
Articles get it through Post Layout, along with the contents rail and reading column. Pages that aren’t articles, like Bookshelf and Fiber Arts, use the page header on its own and build their own layout underneath. Every part except the title is optional, so the same header works for a dated post or an ongoing collection.
Anatomy
Reading archive
Book club picks, Little Library finds, and the occasional summer indulgence.
Digital garden
Bookshelf
A running list of everything I’ve read lately, the good, the great and the “why did I finish that.” Pull up a chair.
Ongoing collection · Since Jan 202664 books · 12 favorites
1Home logoTop left, petals colored per page (utilityStart).
2DescriptionA short title and one italic line, top right (descriptionTitle, description).
3EyebrowA small label with a marker, like “Digital garden” or “Creative Coding”.
4TitleBig, with the page’s accent square (title, accent).
5LedeOptional intro line under the title, like Bookshelf’s.
6Details, leftDate · reading time for posts, or custom text (detailsStart).
7Details, rightTags, or anything custom like a stats button (postTags).
Usage
// An article: PostLayout includes the header
<PostLayout
eyebrow="Creative Coding"
title="Drawing with Code"
publishedDate="Oct 3, 2026"
readingTime="8 min read"
postTags="CSS, Illustration"
contents={[…]}
>
<PostSection …>…</PostSection>
</PostLayout>
// A collection page: the header on its own, your layout below
<PageShell>
<PageHeader
utilityStart={<HomeLogo … />}
descriptionTitle="Reading archive"
description="Book club picks, Little Library finds…"
eyebrow="Digital garden"
title="Bookshelf"
accent="var(--blue-bold)"
lede={<>A running list of everything I’ve read lately…</>}
detailsStart="Ongoing collection · Since Jan 2026"
postTags={<StatsButton />}
/>
<BookshelfBrowser … />
</PageShell>
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.
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>
Navigation
Patterns
How readers find their way around a long page.
Long pages get the same few wayfinding tools, and each one uses the colored markers of the sections it points to. Posts get all of them automatically from Post Layout, and this design system uses the same mobile bar with its group colors.
Contents
On wider screens, a list of sections sits beside the post and stays put as you scroll. Each entry has its section’s marker color, matching the square beside that section’s heading.
Mobile contents bar
On phones, once the Contents list scrolls out of view, a strip of section colors pins to the top. Each segment is as long as its section: finished sections are filled, the current one fills as you scroll, and what’s ahead is faded. The label under it names the current section, and tapping it opens the list so you can jump anywhere. It’s announced as “Contents” with an expanded or collapsed state, closes with Escape, and appears without sliding when reduced motion is on. This design system uses the same bar, with each section in its group’s color.
Every post ends with a row in the same voice as the homepage’s “General nerdery” link. Back to top glides up (or jumps, with reduced motion) and moves focus to the title. The optional next link points to a related post.
For pages nested inside another, a trail of links back up, ending with the current page.
Usage
// Posts: all of it comes from PostLayout.
<PostLayout
contents={[{ id: "the-div", label: "It’s all about the <div>", tone: "sky-bold" }, …]}
next={{ href: "/css-shapes", label: "CSS Shapes" }}
>
<PostSection id="the-div" …>…</PostSection>
</PostLayout>
// Breadcrumbs: an ordered list of TextLinks inside a labeled nav.
<nav aria-label="Breadcrumb">
<ol className="link-breadcrumbs">
<li><TextLink href="/">Home</TextLink></li>
<li className="link-breadcrumb-separator" aria-hidden="true">/</li>
<li><span className="link-breadcrumb-current" aria-current="page">Links</span></li>
</ol>
</nav>
Site Footer
Patterns
The same footer closes every page.
A woven strip of palette colors marks the end of the page, followed by the colophon: where else to find me, how to get in touch, and what the site is built with. Its markers and link hovers take the page accent, so it matches whichever page it ends.
Usage
import SiteFooter from "@/components/layout/SiteFooter/SiteFooter";
// Posts get it automatically from PostLayout (footer={false} to hide it).// Anywhere else, add it at the end of the page:
<SiteFooter />
// Markers and link hovers follow the page accent, or pass one in:
<SiteFooter accent="var(--coral-bold)" />