Vishal.dev
Back
Frontend

Building Frontend Systems That Don't Look Like Another Tailwind Clone

March 18, 2026 3 min read
Next.jsUI SystemsTailwind

Most frontend codebases share a recognizable smell: inconsistent spacing, arbitrary font sizes, ten different shades of gray, and components that look similar but have no shared foundation. This post covers the system I use to keep UI coherent without over-engineering it.

Typography as the Foundation

Pick three typefaces and assign them clear roles:

  • Display: A bold, expressive face for headings. Syne, Space Grotesk, or Inter Display. Used for h1–h3 only.
  • Body: A readable, neutral face for paragraphs and UI text. DM Sans, Inter, or Public Sans.
  • Monospace: For code, data, labels, and any text that needs precision. JetBrains Mono, DM Mono, or IBM Plex Mono.

The key is strict role assignment. If your monospace font starts appearing in body text, or your display font is used for button labels, the hierarchy collapses.

Spacing Rhythm

A spacing scale eliminates guessing. I use powers of 2 with a base of 4px:

// Spacing scale
const space = {
  0: '0px',
  1: '4px',    // micro: icons, badges
  2: '8px',    // tight: button padding, tag gaps
  3: '12px',   // compact: card padding (small)
  4: '16px',   // base: section padding, card padding
  5: '24px',   // relaxed: between sections
  6: '32px',   // wide: between major sections
  7: '48px',   // xl: page section gaps
  8: '64px',   // 2xl: hero spacing
}

The rule: never use a value that isn't in the scale. If the scale doesn't have what you need, extend the scale — don't add one-off values.

The Tag Pattern

One of the most useful UI primitives I've settled on is the tag: a small uppercase label with a subtle border and monospace font. It signals metadata, categories, and context without competing with headings.

.tag {
  font-family: var(--font-mono);
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  padding: 2px 8px;
  border: 0.5px solid var(--border-color);
  border-radius: 4px;
}

Tags work for categories, dates, reading time, tech stack items. Consistent application of this single pattern ties the UI together.

Border as Divider, Not Decoration

Every border in the system should serve a structural purpose. If you're adding a border for visual interest, use a glow or a background color instead. Borders define boundaries between sections, cards, and interactive states.

Use 0.5px borders for subtle separation and 1px borders for interactive elements (cards, buttons, inputs). The half-pixel border is invisible enough that it doesn't clutter but present enough that it structures the layout.

Color Without Chaos

A three-tier color system covers most needs:

  1. Semantic colors: --bg-primary, --text-primary, --accent, --border-color. These adapt to light/dark mode and cover 90% of use cases.
  2. Contextual colors: --card-hover, --btn-bg, --tag-bg. Specific to component states.
  3. Accent colors: One accent color (blue in most cases) used sparingly for interactive elements, links, and highlights. One accent color only — two accents split user attention.

The test: if you can remove a color variable and the UI still works (just less decorated), you're using the right abstraction. If removing it breaks the layout, your color is doing structural work and should be a layout property instead.

What Makes a Frontend Feel Engineered

Three things separate an engineered frontend from an assembled one:

Consistency over creativity. Using the same spacing scale, the same color system, and the same component patterns everywhere creates a cohesive experience. Creativity belongs in the content and interactions, not in the layout system.

Constraints over flexibility. A design system with 3 font sizes (display, body, small) is easier to maintain than one with 12. Remove options, don't add them.

System over components. Building a button component is easy. Building a system where buttons, tags, badges, and pills all follow the same sizing and spacing logic is harder and more valuable.