Skip to main content

How I design

These are the ideas behind how I design my sites and the projects I work on. Most of them came from something I built and later had to rethink, so each section points to the work it came from: the Wisp landing page, the navbar and cursor on this site, the dev notes where I took them apart, and the Substrate launch at the end of the page.

135 pieces published in the last 52 weeks, one dot each.70 pieces published in the last 26 weeks, one dot each. Most recent: A New Page About How I Design, 4 Oct 2026.
Writings
71
Dev notes
21
Quick ships
43
Last published
4 Oct 2026

How I started learning design

I came to design through engineering. For most of the time I have been building software, design was the part I tried to make acceptable before moving on to what I considered the real work. Over the last year that changed, and I have been learning design deliberately, mostly by building interfaces, comparing them with work I admire, and rebuilding the parts that did not hold up.

Apple is the clearest influence, and Steve Jobs’ view of design in particular. He said that design is how it works, and Apple’s short film Design Is How It Works, which takes its title from that idea, is the video that changed how I thought about my own work. I realised that most of what I had been calling design was decoration, applied after the important decisions about how something behaves had already been made somewhere else.

Wisp was the first place I tried to apply that. It was a startup I worked on for deploying AI agents the way developers deploy apps, and although it has since been discontinued, its site still works. I wrote a full design system for it, with bracketed navigation set in Relative Mono, ASCII-drawn diagrams, and a single blue accent, and a large part of this site’s design language still comes from that document.

Looking at the Wisp page now is useful because I would change parts of it. It labels its own sections with tags such as “[Hero]” and “[Features]”, which describe the layout without telling the reader anything. Its feature section opens with “Everything you need to ship production-grade agents”, a sentence that could sit on almost any product page. Neither is a large mistake, but both are the kind of default I now try to catch before shipping, and several of the sections below came from noticing things like them.

Since then I have been experimenting with several interfaces on my own sites: a navbar that behaves like a Dynamic Island, a macOS-style cursor, a daily newspaper assembled from my archive, and the Raster language this page is drawn in. Each experiment taught me something specific, and this page collects those lessons next to the work that produced them.

The thewisp.in landing page: bracketed navigation links, the headline Deploy agents, not servers, a short description, a Join waitlist button, and a dot diagram of agents in a panel on the right.
thewisp.in, first screenThe landing page I designed for Wisp, a serverless platform for deploying AI agents. The headline “Deploy agents, not servers.” sits beside a dot diagram of agents, with one primary button for the waitlist and bracketed Relative Mono links for the blog, FAQ, documentation, and GitHub. The “[Hero]” and “[Features]” tags are the labels I would now remove.

Leaving perfections for people to notice

There is a sentence I keep returning to when I work: leave perfections for people to notice, instead of leaving loopholes and hoping they miss them. Much of design work is defensive. It checks that nothing is broken, that the layout survives a long title, and that the error message exists. That work is necessary, but it only removes reasons to complain. The other half is deliberately adding something good enough that a careful person might stop and look at it, such as the small circle that separates from my navbar with a green tick when you reach the end of an article.

Very few people notice any single detail like that, and I have had to accept it. What I have seen is that people still register the total. They describe a page as feeling right without being able to say why, and that impression is made of dozens of decisions they never consciously saw. So I do not add details to be recognised for them. I add them because I would know if they were missing.

Instagram post titled Soma, up close, with labels pointing to the dendrites, membrane, and nucleus.
Substrate, post 3: Soma, up closeThe nucleus is the smallest part of the mascot, and it still has a job. It is grey while Soma is idle and turns green when a task is done, so a detail most people will skim past also reports real state.

Walking through my own site as a stranger

After a site has been running for a while, I stop seeing its rough edges. I know which areas are clickable and I excuse the awkward ones because I built them. So I periodically go through the whole site as if I had never seen it, with a written checklist based on Apple’s Human Interface Guidelines that turns vague impressions into specific checks.

The last pass found that the search buttons in my navbar were 32px tap targets, which phones miss regularly, so they now sit inside 44px touch areas. The icon strokes were 1.5px and looked washed out against the dark glass of the navbar, so they went up to 2.2px. Buttons now scale to 95% while pressed, archive cards lost labels that repeated what the card already showed, and going back in the browser now returns you to the same page of results instead of the first one.

A screenshot shows where an animation ends, not how it moves

When I turned my navbar into a Dynamic Island, I had the two reference implementations cloned instead of working from screenshots or memory. A screenshot shows the final position of an animation. It does not show the spring, the 250 millisecond delay before the separating shape sharpens, or the 42 pixels the smaller circle travels as it splits away. Those values decide how the interaction feels, and they are only available in the source code.

Studying a reference this closely makes it easier to take the right part of it. I kept the geometry and timing of the split and left out the phone-call screens, caller photos, and app icons that belonged to the original. The navbar moves like the reference and still looks like my website, which is the line I try to hold whenever I learn from someone else’s work.

The navbar island on this siteA recording of the split and the expanded feedback states. The small circle travels 42px away from the navbar, and the stretched shape between them sharpens after a 250 millisecond delay.

Listing what already works before adding anything

Before adding the island, I wrote down everything the navbar already did. It expanded at the top of a page, shrank into a pill after scrolling, opened a drawer on mobile, held the audio player, and drew reading progress around its outline. Each of those behaviours had already been tested, and replacing the navbar wholesale would have meant rebuilding and retesting all of them, so the new states were added around the existing structure.

The same list showed me what to remove. A floating table of contents I built along the way competed with the island for the same space, and it did not survive. Redesigns often go wrong by treating everything old as disposable, and a written list of what already works is the simplest protection I have found against that.

Anything that follows a person constantly has to stay precise

The cursor is an unusual part of an interface. It sits outside the page, but it follows a person through every second they spend on it, so small changes to its movement stay noticeable even when nobody is paying attention to them. When I built a macOS-style cursor for this site, the rule was that it had to behave like a cursor first: pointing, clicking, and selecting text could not become any less precise.

That rule shaped every decision. Movement is smoothed in a way that does not depend on the frame rate, horizontal speed tilts the arrow slightly, and pressing changes its scale, but the I-beam stays upright and still over text because selection needs a stable target. The cursor files I started from contained frames up to 256px, which browsers silently ignore above 128px, so the effect only worked once I extracted frames at a size browsers accept.

The cursor on this siteA recording of the cursor system: frame-rate independent smoothing, a tilt that follows horizontal speed, and an I-beam that stays upright over text.

Visible effort is evidence of care

When Apple made a new intro for Apple TV, they built it from real glass, practical lighting, and in-camera effects, and then published a video showing how it was made. Rendering it would have been cheaper, and most viewers could not have told the difference on screen. The video mattered anyway, because it showed someone choosing the harder way to do a small thing, and that choice says something about how they will handle larger ones.

AI has made a polished surface almost free to produce, so polish on its own no longer tells anyone much. What still carries information is evidence of specific decisions: a layout that only makes sense for its content, a pattern drawn from real data, and copy that names the actual thing. I check for this by imagining my name and content swapped for someone else’s. If the page would still work unchanged, it contains no decisions yet, and I redesign it.

Instagram post titled Meet Soma, explaining that a soma is a neuron’s cell body and that Soma is the Substrate mascot and logo.
Substrate, post 2: Meet SomaSoma is named after a neuron’s cell body, which receives signals and decides whether to fire. The name describes what Substrate’s agents are meant to do, so the mascot comes out of the product itself and could not be moved to another brand.

Judgment is the part I do not hand over

I build much of my software with AI models now. The navbar island was built with GPT 6 Astra in Codex, and the model wrote most of the code. I brought the references, ran each version in the browser, pointed out what felt wrong, and asked for the whole interaction to be checked again after every fix. Most of the design happened in that loop.

I think judgment is the layer that stays human the longest: knowing which reference to study, which constraint matters, when a detail is finished, and when an idea should be removed. The models are fast enough that I can try three directions where I used to try one, which makes choosing between them a larger share of the work.

Software should remember what someone meant to do

If someone spends several minutes setting filters on a dashboard and sends the link to a colleague, the colleague should see the same view. If someone fills out a long form and loses signal as they press submit, their words should still be there when the connection returns. Both failures come from the same mistake, which is treating what a person was doing as temporary memory on one screen.

So I treat the URL as the record of where someone is: filters, pages, and selections live in it, and refreshing or pressing back restores them. Anything a person has typed is kept until it has actually been saved. These are the least visible decisions on any page, and they are the ones people remember most clearly when they go wrong.

Old writing deserves a front page

Most personal sites list writing newest first, which suits news and buries everything else. An essay from two years ago about system design is often as useful as one from yesterday, but in a reverse chronological feed it sits behind pages of pagination that almost nobody opens.

Reprint is my answer to that. Every morning it assembles a twelve-story edition from my essays, dev notes, quick ships, and research notes, and at the end of the day that edition is frozen into a permanent archive. It deliberately uses a different visual language from the rest of the site, a white broadsheet with Playfair Display headlines and Georgia body text, because it is meant to be read like a paper, and the three-column layout uses plain CSS columns with no layout script.

The Reprint front page: the masthead REPRINT in a heavy serif, the date and volume number in a mono dateline, section links, and the lead story set in Playfair Display with a drop cap.
Reprint, the edition for 4 October 2026The front page uses a newspaper’s conventions because they solve the same problem: a masthead, a dateline with the volume and edition number, a lead story with a drop cap, and smaller stories in columns below. The date selector opens any past edition, each frozen as it was printed that day.

A small palette makes mistakes easy to see

This site uses a black background, text in #ededed, and one accent colour, #1479ff, which I keep for links, focus rings, and the most important element on a page. It uses one type family: Relative Sans for reading and Relative Mono for dates, counts, and code. Spacing is built from a 4px unit, and every border is 1px wide.

With no gradients or illustrations to carry a page, alignment and spacing have nowhere to hide. A heading 3px off the grid, or two sections with slightly different gaps, is visible as soon as the page loads. That makes my own errors easy to find, which is the main reason I keep the palette this small.

Instagram post titled Substrate Console listing four numbered steps: check in, consult, prescribe, and sign.
Substrate, post 5: ConsoleFour steps of a clinic visit, numbered 01 to 04, each with one line describing it. White type on black, one pixel typeface for the heading, and thin rules between steps keep the list readable at the size of a phone screen.

Relative, the type family every word here is set in

Relative is my own type family, and every word on this site is set in it. It has three variable fonts: Relative Sans for reading and headings, Relative Rounded for softer interface and branding moments, and Relative Mono for code, dates, and counts. Each has a single weight axis, from 200 to 800 in Sans, 200 to 1000 in Rounded, and 100 to 800 in Mono, so a page can use the exact weight it needs.

Body text on this site is set at weight 460, between Regular and Medium, which only a variable axis allows. Relative Mono gives zero a slash and draws 1, l, and I differently, because code and order numbers are exactly where a misread character causes a real mistake.

Relative SansReading and headings · wght 200 to 800 · 1,188 glyphs
Relative RoundedInterface and branding · wght 200 to 1000 · 1,103 glyphs
Relative MonoCode, dates, and counts · wght 100 to 800 · 1,179 glyphs

Why Raster textures are generated from real data

Raster is the extension this page is drawn in. It takes something continuous, such as a letterform, an image, or a series of numbers, and draws it through dots, squares, or horizontal lines. Each of my sites has its own primitive: dots for writing, squares for projects, and lines for research. Raster never touches body text, buttons, or forms, and the type is set in Geist Pixel, which ships each primitive as a real font, so the words stay selectable and readable by screen readers.

The rule I hold Raster to is that every pattern comes from something real. The dot grid at the top of this page is my publishing record for the last 52 weeks, with one dot for each piece. The halftone behind each mockup is generated from the pixels of that mockup, or of a recording’s still frame. The number beside these sections is redrawn in each section’s primitive when you reach it. A random texture would look identical on another website, while these change when my archive or the work changes.

Instagram post with Soma, a round dark cell with eyes and thin dendrites, above the word Substrate in pixel type and the line One record for your clinic.
Substrate, post 1: introductionThe cells around the edges are drawn from dots in the same way as the Substrate landing page, and they stay at the edges so the wordmark and the line “One record for your clinic” remain the clearest things on the post.

Designing for people who will never read this page

Almost nobody who uses my sites will read any of this, and they should not need to. Each screen has one obvious primary action and no technical terms, so someone with no technical background can use it without help. When something needs complex logic, the logic stays in the code, and the person sees only the choices they need to make.

Accessibility follows from the same idea. Touch targets are at least 44 by 44 pixels even when the icon is smaller, text meets a 4.5:1 contrast ratio, every control works from a keyboard, and every animation has a reduced-motion version. A detail that only works for people with a mouse and good eyesight is a loophole, in the sense of the first section on this page.

Instagram post titled Soma at work, showing Soma idle, done with a green nucleus, and sleeping with closed eyes, each above a line of status text.
Substrate, post 4: Soma at workEach state is paired with a sentence: “Nothing is running.”, “Summary ready.”, and “Processing resumes at 9:00.” Soma sits beside the words and never replaces them, so the status can be read by someone who has never seen the mascot, and by a screen reader.

Patterns I leave out

These patterns are common on websites, and none of them appear on mine:

  1. Gradient text, glowing borders, and blurred colour shapes behind content
  2. Pill badges with a pulsing dot
  3. Uppercase labels above section headings
  4. Rows of three cards, each with an icon in a tinted square
  5. Sections that fade in every time they scroll into view
  6. Numbers that count up from zero when a page loads
  7. Statistics without a date or a source
  8. Background textures that are not generated from anything

Each of these appears on so many websites that it no longer says anything about the page it is on, which is the opposite of what I want a design decision to do.

Substrate: landing page and launch series

I designed the landing page and a six-post Instagram series for the launch of Substrate. Its first product, Substrate Console, keeps one record for a clinic, from the patient queue to the prescription. Soma is the name for the AI agents planned to work from that record, and it is also the mascot and the logo. Four of the six posts appear earlier on this page next to the principle each one shows; the landing page and the last post are below.

The substrates.in hero: dot-sampled spheres joined by thin lines above the words Substrate Console in large pixel type, with a one-line description, product status, and an email field with a Join waitlist button.
substrates.in, first screenEverything needed to join the waitlist is in the first screen: the product name, a one-line description, the status of each product, and an email field. The status line says “Console, opening first” and “Soma, coming soon”, so nobody has to guess which part exists today.
Instagram post titled Join the waitlist, with Soma above the heading and a button reading substrates.in.
Substrate, post 6: waitlistThe last post has one action. It says plainly that Substrate Console is not open to clinics yet, and it points to substrates.in, where people can leave an email address and be told when access opens.

The spheres above the name are drawn from dots and joined by thin lines, which suggests cells and signals without showing anything medical directly. I left out crosses, stethoscopes, and photographs of doctors because health products use them so often that they all look alike at the size of a thumbnail, and the dots and pixel type keep Substrate recognisable at that size.

The posts are a sequence meant to be read in order on a profile grid: the name, the mascot, how the mascot works, its states, the product, and the waitlist. Each post has one heading in the pixel typeface and at most two lines of supporting text, because most people give a post a few seconds before scrolling on.

Soma’s states were the decision that mattered most. A mascot that changes expression is easy to like and easy to misread, so every state is tied to a sentence that says the same thing in words. The mascot makes the status quicker to recognise for people who know it, and the sentence keeps it readable for everyone else.

What comes next

This page will change as I learn. Raster and the Substrate work are the newest parts of it, and I expect at least one idea here to turn out wrong once I have used it on more projects. When that happens, I will rewrite the section and say what changed, so the reasoning behind the change stays on the page.

Most of the work behind these ideas is written up in more detail elsewhere. The dev notes cover how each interface was built, the writings cover why I care about the things I do, and the projects site shows the finished work.