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 that the model has no taste and defaults to something safe. Both are true. I hit both, repeatedly, for eight days.

Neither one was what slowed me down. I can see that something is wrong about half a second after it renders, and it takes me around two minutes to say why in words the model can act on. That gap is where the time actually went, and it belongs to me rather than to the model. 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 wrote 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. 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 is 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 it maps to concrete CSS. I think in materials, light, and weight. The AI thinks in properties. Physical description is the bridge between the two.

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 to light, top to bottom, reads as a recess lit from above. The bright hairline underneath is the light catching the bottom lip. Once I’d described it that way, “add a module groove between these sections” landed in one shot. The metaphor was the spec.

Which points at the second lesson: name your patterns. Module grooves. Hardware buttons. Bolt corners. The moment I gave things names, the AI locked onto them and stayed consistent. We were building a design system out of vocabulary, and it’s the single most useful thing I did the whole time. 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. It 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 without rendering it. “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 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. In isolation 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. And that reframed how I work with the AI entirely. A version that would cost a human collaborator a day costs the AI two minutes, so overshooting is cheap. Now I ask for too much on purpose, look at it, and cut back. The bad version isn’t waste. It’s the thing that shows me what I actually want.

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, 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 repeat them. Repeating yourself isn’t waste here, it’s maintenance.

Dark mode broke everything, every time. It needs its own contrast relationships and shadow intensities, tuned element by element, not the light palette flipped.

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.” Same person, same taste, different vocabulary. 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 or running mix-blend-mode: overlay on a noise texture. 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