Yasiru Senarathna Logo
All ArticlesDevelopment
June 19, 2026

Simple Tailwind CSS Patterns That Changed How I Build UIs

You don't need to write highly complex CSS to build great interfaces. Here are the foundational Tailwind patterns, from Flexbox basics to CVA, that make a massive difference in my daily workflow.

StitchFlow dashboard showing real-time stochastic analysis

Mastering the Box Model first

When I first learned Tailwind, my first design lead, Hiru, gave me a piece of advice that stuck with me: "Look at everything in a design and break it into boxes."

That advice helped me immensely when learning my first major Tailwind concept: Flexbox. Before you try to master anything flashy, you have to master this. Once you understand how Flexbox and CSS Grid dictate the structural flow of your interface, Tailwind transforms from a confusing list of abbreviations into a highly expressive design API.


Cleaning up components with CVA

The biggest complaint developers have about Tailwind is "HTML clutter." If you have a button with primary, secondary, destructive, and outline variants, managing all those ternary operators inside your React components becomes unreadable very quickly.

While it looks advanced, using class-variance-authority (cva) is actually a very simple way to fix this. It allows you to map your Figma component variants directly into your code, keeping the JSX clean and the styling logic centralized.

Button.tsx
import { cva } from "class-variance-authority";

const buttonVariants = cva(
"inline-flex items-center justify-center font-medium transition-colors focus:ring-2 focus:outline-none",
{
variants: {
intent: {
primary: "bg-[#111111] text-white hover:bg-zinc-800",
secondary: "bg-zinc-100 text-[#111111] hover:bg-zinc-200",
danger: "bg-red-500 text-white hover:bg-red-600",
},
size: {
sm: "h-9 px-4 text-sm",
md: "h-11 px-6 text-base",
}
},
defaultVariants: {
intent: "primary",
size: "md",
}
}
);
Live Output

State management without JavaScript

Many developers immediately reach for React state (useState) to handle simple UI changes—like highlighting a card when a child link is hovered, or showing an error icon when a form input is invalid.

Tailwind's group and peer modifiers allow you to handle these states purely in CSS. Using peer-invalid:block on an error message allows you to show the error based natively on the HTML input's validation state, completely eliminating unnecessary React code.


Perfecting accessibility with Focus Rings

A dead giveaway of a poorly coded UI is the default, ugly blue browser outline when you tab through a form. Conversely, a dead giveaway of a poorly accessible UI is when a developer removes the outline entirely without replacing it.

I use a very simple combination of classes on almost every interactive element: focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2. It hides the default browser style and replaces it with a clean, branded ring that only shows when a user is navigating via keyboard, leaving mouse users undisturbed.

Input.tsx
{/* The ring offsets push the focus state just outside the border */}
<input
type="text"
className="border border-zinc-300 px-4 py-2 rounded-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#0d3bf0] focus-visible:ring-offset-2"
/>

Editorial control with Line Clamp

When building bento grids or article cards, content length is unpredictable. If an article title is too long, it will break your carefully crafted grid layout.

Instead of writing complex CSS text-overflow logic or truncating strings with JavaScript (which ruins SEO), just use line-clamp-2 or line-clamp-3. It perfectly truncates the text with an ellipsis after a specific number of lines, maintaining your layout while keeping the full text in the DOM.


Breaking the grid with Arbitrary Values

Tailwind forces consistency through its design system, which is great 90% of the time. But as a designer, there are moments when you need an element positioned exactly17px off-axis to visually balance the composition, or a highly specific gradient stop that isn't in your theme.

Instead of writing custom CSS classes for these one-off exceptions, I use arbitrary values: top-[17px] or bg-[#F3F31F]. It keeps all styling contained in the class attribute, ensuring that when the component is deleted, the CSS is inherently deleted with it. No dead code.

Card.tsx
<div className="relative p-6 bg-zinc-100 border border-zinc-200 w-64 h-32">
<h3 className="font-medium text-[#111111]">Project Alpha</h3>
<p className="text-sm text-zinc-500 mt-1">In progress</p>

  {/* Using arbitrary values for absolute precision */}
<div className="absolute top-[-8px] right-[-8px] w-4 h-4 bg-[#0d3bf0] rounded-full" />
</div>

Project Alpha

In progress


The short version

Tailwind is incredibly powerful, but it doesn't require complex trickery to be effective. Master the box model first. Use tools like cva to organize your component logic, leverage native utilities like peer and line-clamp to reduce JavaScript dependency, and use arbitrary values when pixel-perfect precision demands it.


Yasiru Senarathna
Yasiru SenarathnaUI/UX Designer & Marketer

I design and build digital products from Sri Lanka, bridging the gap between clean, minimalist aesthetics and scalable frontend code. Want to discuss frontend architecture? Feel free to reach out.

Back to Articles