Our services
If you can think it, we can make it brainsoft.
If you can think it, we can make it brainsoft.
Written By: BrainSoft In Frontend
Every stylesheet starts clean. Then three people add features for half a year, a redesign lands on one page, and suddenly nobody wants to touch the button styles because something on the checkout screen breaks when they do. That is not a talent problem. It is a structure problem, and it shows up in the same few places every time.
The short version: keep one source of truth for colours and spacing, give components names that describe them instead of their position, scope styles to the thing that owns them, and delete dead rules on a schedule. Do those four things and the file stays editable at month six.
The fastest way to lose control is hex codes scattered across forty files. Someone changes the brand blue, finds thirty-eight of them, and the other two become a bug report three weeks later. Custom properties fix this without a build step.
:root {
--color-brand: #1d4ed8;
--color-text: #0f172a;
--color-muted: #64748b;
--space-2: 0.5rem;
--space-4: 1rem;
--radius: 0.5rem;
}
Then components read from those. If a designer hands you a one-off shade, define a new token for it rather than inlining the hex. The rule I follow: a raw colour value should appear exactly once in the codebase, in the token block. Same for spacing scales and radii. It feels fussy for the first week and then it stops mattering, which is the point.
Class names like .left-column, .mt-20, or .blue-button age badly. The column moves, the margin gets overridden, the button turns green. Names that describe what the element is survive layout changes.
.invoice-summary instead of .right-sidebar.card--featured instead of .card-big.nav-link instead of .header aScoping matters just as much. Descendant selectors like .content p look harmless until you drop a component into that container and it inherits typography you did not ask for. Prefer a single class per element and let that class carry the styles. If your framework supports native nesting or scoped styles, use them, but the discipline is the same either way: a component should not need to know where it is rendered.
.invoice-summary {
border: 1px solid var(--color-muted);
border-radius: var(--radius);
padding: var(--space-4);
}
.invoice-summary__total {
color: var(--color-text);
font-weight: 600;
}
Most CSS rot is a specificity fight. Someone needs to override a rule, so they add an id, then a !important, then a wrapper class, and now the next person needs two of those. The fix is to keep every selector at roughly the same weight so the last one in the file wins and you can reason about it.
Practically that means avoiding ids in stylesheets, avoiding !important outside of utility overrides, and avoiding deep descendant chains. If you find yourself needing three classes to beat one existing rule, that existing rule is probably too broad and should be narrowed instead. Utilities are fine here, and so is a component layer, but pick one approach per project rather than mixing them page by page.
Unused rules do not crash anything, so they accumulate quietly and make every search result ambiguous. Once a quarter, grep for a handful of class names and delete the ones with no hits. If the project is large enough, a tool that reports unused selectors against your built HTML is worth the setup. The same goes for the token block: if a colour has not been referenced in months, remove it.
Pair that with a visual check on the pages that matter. A screenshot diff on the five or six key screens catches the accidental global change that a unit test never will. It is a small amount of infrastructure and it pays for itself the first time someone edits a shared class. If setting that up sounds like a chore, it is the sort of thing our services team does as part of ordinary frontend work.
Either works if you commit to it. Utilities keep specificity flat and make changes local, but they get noisy in markup. Custom CSS reads better for complex components but needs naming discipline. The failure mode is mixing both approaches across the same components, which is what makes overrides painful later.
Search the codebase for the class name, including templates and any string-concatenated variants. If nothing references it, remove it and run your screenshot diff. Anything that fails the diff goes back. Doing this in small batches is safer than one large cleanup commit.
Yes, and they cost almost nothing. Even six custom properties for colour, spacing, and radius mean a rebrand is a one-file change instead of a search-and-replace across the whole stylesheet. The overhead is one block at the top of a file.