Web Frontend Code Quality
Follow these rules to write high-quality frontend code. We’ll assume the framework is React, but most of this applies to any HTML/CSS/JS on the web.
Styling
Section titled “Styling”For this reference, I’ll be using Tailwind for simplicity (still broadly applicable to any CSS framework).
You Don’t Have to Use Flex/Grid
Section titled “You Don’t Have to Use Flex/Grid”This is a bit of a pet-peeve of mine, and I even see it in starter kits.
Here are two example components:
<div className="flex flex-col gap-1"> <label htmlFor="email">Email</label> <input name="email" /></div>
<div className="grid gap-1"> <label htmlFor="email">Email</label> <input name="email" /></div>Both of these work fine, they’re just unecessary. In the worst case, nested grid layouts can behave in unexpected ways, especially when elements change size. A cleaner, and less expensive method:
<div className="space-y-1"> <label htmlFor="email">Email</label> <input name="email" /></div>The space utility applies margin to the children elements to space them out, avoiding the need for flexbox / grid.
Nested Border Radius
Section titled “Nested Border Radius”Another pet-peeve. Once you see it, you can’t unsee it. Here’s a Tailwind snippet that makes nested borders concentric and pixel perfect.
<div class="rounded-(--card-radius) p-(--card-padding) outline [--card-padding:--spacing(3)] [--card-radius:var(--radius-4xl)]"> <img class="aspect-square rounded-[calc(var(--card-radius)-var(--card-padding))] object-cover outline" src="" alt="" /></div>While a bit verbose, this is nice because:
- Declaring the variables looks very intentional, which helps maintainers understand things
- It’s parametric, so when the theme is updated, the code doesn’t need to be
Component Variants
Section titled “Component Variants”cn (usually shadcn’s wrapper for classnames or twMerge) is a great utility for composing styles. However, I’ve seen it misused for creating variations of components. We actually have a tool for that, it’s called cva (class-variance-authority).
Here’s an bad example of creating component variants:
function badgeVariants(value: string): string { return cn( "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium", value === 'open' && 'border-emerald-200 bg-emerald-50 text-emerald-700', value === 'responded' && 'border-yellow-200 bg-yellow-50 text-yellow-700', value === 'ignored' && 'border-gray-200 bg-gray-50 text-gray-700', );}Here’s a tidier version that can also benefit from strict type checking:
const badgeVariants = cva( 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium', { variants: { status: { open: 'border-emerald-200 bg-emerald-50 text-emerald-700', responded: 'border-yellow-200 bg-yellow-50 text-yellow-700', ignored: 'border-gray-200 bg-gray-50 text-gray-700', }, }, });It also makes it far easier to define complex variants, without complex and error-prone boolean logic. Read the cva docs for more info.
React Specifics
Section titled “React Specifics”Side Effects
Section titled “Side Effects”This is the best guide for why and how to remove unnecessary effects from React code: https://react.dev/learn/you-might-not-need-an-effect
There are plenty of problems that can be introduced through using the ‘quick hack’ of a useEffect to fix things. After all, React is a functional paradigm. The docs link above goes through it best, but in summary:
You probably don’t need a useEffect.
Data fetching should probably use a library like tanstack-query or RSC (if ya retarded).
There are still valid use-cases:
- Interacting with external APIs is fine, like the browser geolocation API - this is what side effects are for (even Tanstack uses them under the hood).
- Timers, cleanups, animations, transitions, etc.
Some useEffect is still necessary.
We need to use it in the right places, and avoiding incorrect usage.
Memoised Callbacks
Section titled “Memoised Callbacks”Not necessary, unless performance intensive. Having it in may lead to slightly worse performance. Useful if you need to maintain referential equality, i.e. you’re passing the function reference to a useMemo dependency array and you don’t want the reference to change on every re-render.
React compliler actually optimises code past the need for useMemo and useCallback these days, so they’re certainly less useful than they once were.
Accessibility
Section titled “Accessibility”Accessibility screen readers forced colours reduced motion