I Described a Hardware UI Into Existence

Building a skeuomorphic interface in pure CSS, no reference image, just me telling an AI what I wanted until it was right.

TypeStaxJuly 20265 min read

Ask a designer why AI still can’t do good visual work and you’ll hear two things: the model has no taste, and it defaults to something safe. Both are true. I hit both, repeatedly, for eight days.

Neither one is what actually slowed me down. I can see that something is wrong about half a second after it renders. It then takes me around two minutes to say why in words the model can act on. That gap is where the time went, and it’s mine, not the model’s. A designer who can’t close it accepts the first output, because they have no second sentence to offer.

I’ve been writing CSS since 2004, and around 2016 I moved into UX and product design. I can still make a surface pixel-perfect. What I can’t build is the machine behind it: components, state, data fetching. So when I started using AI to write the code, the question was whether I could steer the visual layer through words alone, at the precision I’d hold myself to if I were writing the CSS by hand.

The test case was TypeStax, a typographic scale generator I’d been building. I gave it an industrial hi-fi look: rotary knobs, embossed slider caps, corner bolts, surface noise, seams between the panels. Skeuomorphic, meaning the interface pretends to be a physical object. It ended up looking like rack-mounted studio gear, all of it CSS and SVG. And I set one rule: no reference images. No Dribbble board, no screenshots, no “match this Figma comp.”

TypeStax rendered entirely in CSS and SVG, with rotary knobs, embossed slider caps and corner bolts
TypeStax, all CSS and SVG

Why the rule? Because a reference lets you cheat. Hand the AI a comp and it pixel-matches, and you never have to say what you actually mean. Take the image away and every decision has to become a sentence. Turning what my eye catches in half a second into words specific enough to act on is the skill I wanted to practice. Here’s what that taught me.

Prompt in physical terms

“Make it premium” gets you nothing. “A 3px channel pressed into the surface, lit from above” gets you the right shadows, because a channel lit from above maps directly onto CSS properties. I think in materials, light and weight, and the model thinks in properties, so the physical description is the only sentence both of us can read.

The seams between panels came from exactly that sentence:

.module-groove {
  height: 3px;
  background: linear-gradient(to bottom,
    oklch(0.85 0.003 80) 0%,
    oklch(0.96 0.003 80) 100%
  );
  box-shadow:
    inset 0 1px 2px oklch(0 0 0 / 12%),
    0 1px 0 oklch(1 0 0 / 50%);
}

Dark at the top, light at the bottom, which is what a recess lit from above looks like. The bright hairline underneath is light catching the bottom lip. Once I’d described it that way, “add a module groove between these sections” landed in one shot, and I never had to describe it again.

Which points at the second lesson: name your patterns. Module grooves, hardware buttons, bolt corners. The moment things had names, the AI locked onto them and stayed consistent, and we were building a design system out of vocabulary without either of us calling it that. It’s the single most useful thing I did in eight days. If you do sustained visual work through conversation, name your patterns on day one.

Name the fault, not the feeling

I’d look at a slider, decide “that needs to be darker,” say exactly that, and it would still look wrong afterward. Darkness wasn’t the problem. The problem was contrast against the track, or tick marks at 50% opacity fighting the thumb for attention. “The thumb should be darker than the track but not as dark as the panel border” is a prompt that works. “Darker” is a wish.

That prompt also shows the other habit that fixed my output: describe relationships, not values. “Make the thumb stone-500” means nothing to me. I don’t think in Tailwind shade numbers, and I can’t tell if it’s right until it renders. “Darker than the track, lighter than the panel edge” I can check by looking. The AI turns a relationship into an absolute value far better than I can pick the value blind.

And one change per round. This goes against every instinct I have, because I see all the problems at once. But change three things and you can’t tell which one helped. So it’s “darker thumb,” then “quieter ticks,” then “softer shadow,” in sequence. Painfully slow, and it converges every time. Whenever I batched three fixes together I went in circles for twenty minutes.

Overshoot on purpose

The rotary knob taught me this one by wasting my time first. I started ambitious: 48 grip ridges from a conic gradient, a brushed-metal face, a specular highlight. On its own it looked genuinely metallic. Next to the flat buttons and plain sliders, it looked like it had wandered in from a different app. All of it got deleted.

The rejected rotary knob, with 48 grip ridges, a brushed-metal face and a specular highlight
The rejected knob

I couldn’t have reasoned my way to the clean version. I had to see the overwrought one to find the line. A version that would cost a human collaborator a day costs the AI two minutes, so overshooting is cheap, and now I ask for too much on purpose, look at it, and cut back. The knob I deleted is what told me where the line was.

What got in the way

The AI defaults to tasteful and safe. My first prompts for skeuomorphic buttons got me softly rounded rectangles with one drop shadow. I had to push hard before it took any risk, and then it overcorrected. There’s no middle gear. Every session had the same rhythm: ask for more, get too much, spend eight prompts dialing it back. The dialing back is where the actual design happens.

Long sessions rot. Forty prompts in, the AI would start contradicting decisions we’d settled early on, re-adding a border I’d removed or reaching for rgba when we’d agreed on OKLCH. I learned to front-load the constraints and then say them again every dozen prompts, which felt like nagging and worked.

Dark mode broke everything, every time. It needs its own contrast relationships and shadow intensities, tuned element by element. You cannot flip the light palette and expect it to hold.

The same hardware buttons rendered in light mode and dark mode side by side
The same buttons, both modes

And the AI never pushes back. If I’d asked for a lens flare on the knob, I’d have gotten a lens flare on the knob. All the taste filtering falls on me, in real time. That’s the genuinely tiring part.

What carried over

The gap between seeing the fault and naming it closed measurably, and it took about eight days. On day one I was saying “make the button look more like a real button” and hoping. By day eight I was saying “add a 3px inset shadow at 12% opacity on the top edge to match the groove depth.” Nothing about my taste changed in that week, only the vocabulary I had for it. It’s a real skill with a real learning curve, and nobody is teaching it.

Eighteen years of CSS got more useful, not less. The AI showed me techniques I’d never reached for, like stacking five box-shadows on one element or running mix-blend-mode: overlay on a noise texture so the grain picks up the colour underneath it. But because I understood the medium, I could catch the subtly wrong version and push for a fifth pass where someone without the background would have accepted the first. The AI handles execution. The judgment about how much depth is enough is still mine.

The knob is satisfying to drag. I described it into existence, and never once pointed at a picture.

All notes