Affordances: The Missing Layer in Frontend Architecture (opens in new tab)

fractaledmind.com·11w·Open original (opens in new tab)

I was building a form with a file input. Nothing fancy—just a place for users to upload a document. I wanted the trigger to look like the other buttons on the page: the same subtle shadows, the same hover effects, the same spacing. I was using Catalyst, the component kit from Tailwind Labs, so I had a <Button> component with all those styles baked in.

But I couldn’t use it.

A file input needs a <label> as its clickable element—that’s how you style file inputs without fighting the browser’s native UI. But Catalyst’s <Button> component only renders as a <button> element or a <Link>. There’s no way to apply those styles to a <label>.

Some component libraries offer escape hatches—props like asChild or render that let you swap out the underlying element. But these props don’t just pass through styles; they pass through the component’s behavior too. That’s fine when you want both. But when you just need the look—when you need an element to appear clickable while retaining its own native semantics—components leave you stuck.

This isn’t a bug in Catalyst. It’s a structural limitation of components as an abstraction. Components are poor vehicles for purely visual styles. And once you see this, you start seeing it everywhere.


Three Things, One Name

Consider the word “button.” In frontend development, it actually refers to three genuinely distinct things:

  1. The <button> element — native HTML semantics and behavior
  2. The Button component — your library’s encapsulation of structure, behavior, and possibly styles
  3. The button visual pattern — rounded corners, padding, solid background, hover states; what makes something look clickable

When you need to make a <label> look like a <button>, you can’t reach for an element or a component.

The same is true for text inputs. There’s the element, the component, and the visual pattern—the border, the focus ring, the placeholder styling—that makes something look like a place to type. You might need that pattern on a <textarea>, a <select>, or a custom autocomplete built on a different element entirely.

These visual patterns have a name in design theory: affordances—visual signals that communicate how an element can be interacted with. The term comes from Don Norman’s The Design of Everyday Things, where he described how the shape of a door handle tells you whether to push or pull. In interfaces, affordances are what make a button look pressable, an input look typeable, a link look clickable.

The standard frontend architecture today needs to add this as the fourth conceptual layer:

Loading more...

Keyboard Shortcuts

Navigation
Next / previous item
j/k
Open post
oorEnter
Preview post
v
Post Actions
Love post
a
Like post
l
Dislike post
d
Undo reaction
u
Save / unsave
s
Recommendations
Add interest / feed
Enter
Not interested
x
Go to
Home
gh
Interests
gi
Feeds
gf
Likes
gl
History
gy
Changelog
gc
Settings
gs
Browse
gb
Search
/
General
Show this help
?
Submit feedback
!
Close modal / unfocus
Esc

Press ? anytime to show this help