In software engineering, I think of taste as a "refusal ledger": a running record of everything you decided not to ship. The spring that doesn't overshoot, the second accent color that never appears, the toast that never fires.
Plenty of people file taste under unteachable instinct. I don't buy it. I'm an engineer, so I trust what I can write down (which is a polite way of saying I distrust vibes).
Taste is a record of decisions, and decisions can be audited, copied and taught. When an interface feels magical, it's rarely because someone added something extraordinary. It's because someone threw out hundreds of small distractions.
Five refusals, and how I check each one.
Taste in software comes down to five refusals
Taste in software engineering comes down to five refusals: spring motion that keeps the user's velocity, optical alignment over the ruler, rules the compiler enforces, a frame budget spent on purpose and quiet UI.
They're my opinions rather than a study, and a few examples come from this site's code.
| Area | Default | Disciplined |
|---|---|---|
| Motion | Fixed keyframes with showy overshoot | Springs that keep the user's velocity |
| Optical alignment | Whatever the inspector reports | Whatever the eye reads |
| Rules | A wiki page | A type error |
| Performance | A cleanup ticket after launch | A frame budget from the start |
| Feedback | Toasts and confetti | Inline state and a soft sound |
None of the right-hand column is free. Each refusal costs a feature someone could have admired (a demo, a flourish, a bit of applause).
1. Spring motion should remember the thumb
Say a user flicks a sheet closed, changes their mind halfway and drags it back.
A keyframe animation is a fixed timeline that can't see the user's momentum, so the sheet follows the old curve or restarts a new one, and either way it jumps.
Why? Because the timeline never knew the thumb was moving. A spring keeps the user's velocity, so an interrupted gesture picks up from the thumb's momentum.
Keyframes
Spring
Press it twice, fast, to change your mind halfway.
I'd start a sheet from a spring like this one (an illustration; the Dynamic Island table of contents on this site uses 300 and 25, a touch livelier):
interface SpringConfig {
readonly stiffness: number
readonly damping: number
readonly mass: number
}
// Crisp, responsive sheet transition without cartoony bounce
export const SHEET_SPRING: SpringConfig = {
stiffness: 300,
damping: 28,
mass: 1.0,
} as constThe motion is a damped spring, F = -kx - c·v, with stiffness k, damping c and velocity v. With stiffness 300, damping 28 and mass 1, the damping ratio is ζ = c / (2√(k·m)) = 28 / (2√300) ≈ 0.81.
That's slightly underdamped: about 1.3% overshoot, enough to feel alive and too little to read as bounce.
Springs take tuning time, and a spinner nobody can grab doesn't need physics. I just don't trust a fixed timeline for anything a thumb can touch.
Good motion doesn't ask to be admired – it tells the eye where an element came from and where it went.
Ten-second test: "If I interrupt this halfway, does it keep the velocity I gave it?"
2. Trust optical alignment over the ruler
When the inspector and the eye disagree, I trust the eye.
Picture a reviewer whose only comment is "the play icon looks off-center." You open the inspector and the triangle is mathematically centered.
The default reply is "the numbers say it's centered." (The numbers are right, and so is the reviewer. That's the annoying part.)
The inspector measures bounding boxes, and the eye measures visual weight. A triangle's weight sits in its wide side, so a box-centered triangle looks off.
The bounding box is centered, so the triangle reads left.
Type designers correct for it on purpose. Why do round letters overshoot the baseline? Because a circle drawn at the same height as a square looks smaller.
I correct for perception, then write the correction down:
- Optical padding: an icon with less visual mass than its neighboring text often needs a nudge of a pixel or two to look baseline-aligned.
- Corner smoothing: CSS
border-radiusjoins a circular arc to a straight edge, so curvature jumps at the seam and the eye reads a flat spot. A squircle eases the curvature in. - Reading measure: Bringhurst's ideal is 66 characters, with 45 to 75 satisfactory.1 This site's column runs wider, by my rough count 80 to 90, inside Butterick's 45 to 90 and above Bringhurst's 75.
If a nudge doesn't survive a squint, it's noise, and I'd rather delete it. Trust the ruler until the eye disagrees, then trust the eye.
Ten-second test: "Does it look centered to me, or does it only measure centered?"
3. Make the compiler say no
Six months after a design review settles on a comfortable line length, a teammate adds a wider container for a new page. A wiki note won't stop that. A compiler will.
The default fix is a note in the wiki or a page in Figma, and it drifts because reading it is optional. (Nobody opens the wiki on a Friday afternoon.)
The disciplined version moves the rule into the type system, so a bad decision fails to compile and quality becomes the path of least resistance.
In TypeScript, the rule could look like this (an illustration of how I'd encode it, not code from this site):
// Illustration: prose containers may only use approved measures.
type ReadingMeasure = 550 | 600 | 650 | 692 | 720
interface ArticleTokens {
readonly maxMeasurePx: ReadingMeasure
readonly baseFontSizePx: 16
readonly bodyLeadingRatio: 1.5
readonly paragraphCadencePx: 26
}
export const ARTICLE_TOKENS: ArticleTokens = {
maxMeasurePx: 692,
baseFontSizePx: 16,
bodyLeadingRatio: 1.5,
paragraphCadencePx: 26,
}I capped this site's article column at 692 pixels. In a system typed like the one above, a stray 700 wouldn't compile.
Types only catch what you can encode, though. They can't tell you whether the page looks right, and a rule that blocks too much just collects workarounds.
4. Performance is part of the look
A 60 Hz screen gives every frame about 16.7 milliseconds, and a museum-grade layout that misses that budget is a broken instrument. Frame rate and input latency are aesthetic qualities, not just technical KPIs.
Let response lag grow and direct manipulation starts to feel indirect: the user stops touching an object and remembers they're talking to a browser parsing JavaScript.
Say a team ships a beautiful scroll effect and plans to profile it after launch. That makes speed a cleanup ticket, and by then the effect often rests on layout work that can't fit the budget.
I try to spend the 16.7 milliseconds on purpose:
- Offload continuous deformation and fluid physics to shaders (WebGL / WebGPU) or worker threads.
- Keep layout queries such as
getBoundingClientRectapart from mutations to avoid main-thread thrashing. - Animate CSS transforms and opacity rather than
width,heightandtop.
A budget isn't a mandate to optimize everything. A hover state on a settings page can spend a few extra milliseconds. The animation a thumb can grab can't.
5. Quiet software is the final test
Noise is everywhere now: visual clutter, stacked notifications, aggressive AI tooltips demanding attention. I think the highest form of taste is quietness.
Say a user saves a record. The default is a toast that slides in, a checkmark that draws itself and a confetti burst, all to announce what the button already showed.
That's software asking for praise for doing its job, and the toast covers whatever the user wants to see next.
Quiet UI does its job, answers with a tactile cue and gets out of the way. The button settles and the inline state changes.
Quiet doesn't mean silent. A failed save or a destructive action should interrupt, because staying quiet there is negligence.
Ten-second test: "If I deleted this notification, would anyone miss it?"
A ledger of refusals can go wrong, too. Refuse everything and you've shipped nothing, so I only count a refusal as taste when it makes the thing easier to use.
When every element has a reason to be there, software reads as a tool you can think through instead of decoration. Trackshot (a concept) is my clearest attempt: it would index screenshots in the background without asking for attention.
Keep the ledger: every no is an entry, and the entries are the taste.
Footnotes
-
Robert Bringhurst, The Elements of Typographic Style, as adapted for the web by Richard Rutter: Choose a comfortable measure. Matthew Butterick's Line length allows a wider 45 to 90. ↩