Carom · LLMs: a cause of, and solution to, UI drift
Software has a natural tendency to drift and sprawl, starting with a pristine design but ending up with a hodgepodge of similar but distinct ways of displaying data or accomplishing tasks. Avoiding that drift requires deliberate effort, and fixing it after the fact is hard. Fortunately, an LLM used right is a great tool to reduce drift.
I suspect the design system drift challenge is growing, not shrinking, with LLM-supported engineering. Human-created drift—the kind we addressed here—happens for a few unavoidable reasons:
Seemingly trivial new components or tweaks get hard-coded, not abstracted
We overestimate the value of conformity, preferring to craft the perfect new design every time
Designing components flexible enough to handle the variety of a large product is hard
Even on a single-developer project, it’s hard to remember every component, so duplication is inevitable
Combatting this is laziness, if nothing else: when writing code takes time, engineers are inclined toward reuse.
LLMs may not have our human love of craft, but they share our limited context windows, and notably their cost to produce code is effectively zero—so the impulse toward laziness that causes us not to write more code than necessary is missing.
The first step is admitting you have a problem
Accumulating UI cruft is like gaining weight: it happens so slowly at first that you may not realize it’s happening, and by the time it’s impossible to deny, it’s also hard to undo. Bit by bit we added similar components, telling ourselves that each was different enough to deserve its own special treatment, or maybe just convincing ourselves it was okay to hard-code one more component rather than do the more difficult work of creating a global abstraction.
That’s how we ended up here:
A person on an email threadDHDiane Halsted<br>A removable person chipDHDiane Halsted<br>A teammate in the activity feedACAlex Chen<br>A contact in the activity feedDHDiane Halsted<br>A team in the activity feedLeadership<br>What a task is linked toHalsted Law Group<br>A milestone on a taskQ3 launch<br>How many threads a file is in3 threads<br>Who sent a file (list view)Alex Chen<br>Who sent a file (grid view)Alex Chen
A dozen chips and tokens and other ways to display an entity’s name.
In a vacuum, none of these is bad. Even taken together, they’re not terrible: these (mostly) read as being members of the same family, and some of the places they diverge help them fit into the surrounding interface. But the net effect of these and similar inconsistencies is a feeling of inconsistency or even carelessness, along with more complexity for engineers who have to decide which approach to take for the next similar instance.
Standardizing our entity tokens across the UI
Before LLMs, standardizing these approaches would have been a straightforward but slow task. LLMs (in this case, Opus 4.8 in Claude Code) can streamline that work, but they can’t yet independently deliver something that meets our standards. Here’s the approach that worked:
Catalog—exhaustively—before touching anything. We had the LLM review every surface for any component that could plausibly be migrated to a new standard entity token. Guardrails that impose structure and process are helpful to make sure the search doesn’t miss anything—spin up an agent per module, outline the types of UI elements that might qualify, and push for false positives over false negatives. If the LLM doesn’t propose anything that you reject as not fitting the pattern, there’s a good chance you haven’t scanned extensively enough.
Collect your artifacts. We’ve found one of the best ways to make the LLM “thoughtful” is to build a styleguide in collaboration with our AI agents. It lets us spend time polishing one small surface until it shines rather than optimize for rapid changes. We wrote every one of our disparate token components to the styleguide, mapped it to a common alternative, and had a single place to assess the changes as we made them.
Think siblings, not identical twins. You don’t need (and shouldn’t want) to drive all differences out of your components. We’re striving for a clear design family—an unmistakable sense that everything we show is part of one harmonious interface, not that we are building a UI with the smallest possible set of tools.
After quite a bit of iteration, here’s what we ended up with in our styleguide:
BeforeAfter<br>A person on an email threadDHDiane HalstedDHDiane Halsted<br>A removable person chipDHDiane HalstedDHDiane Halsted<br>A teammate in the activity feedACAlex ChenACAlex Chen<br>A contact in the activity feedDHDiane HalstedDHDiane Halsted<br>A team in the activity feedLeadershipLeadership<br>What a task is linked toHalsted Law GroupHalsted Law Group<br>A milestone on a taskQ3 launchQ3 launch<br>How many threads a file is in3 threads3 threads<br>Who sent a file (list view)Alex ChenACAlex Chen<br>Who sent a file (grid view)Alex ChenACAlex Chen<br>Full-width list rows<br>A...