EmpireUI
Get Pro
← Blog7 min read#bauhaus-design#react-ui#tailwind-css

Bauhaus Design in React: Form Follows Function in UI

Bauhaus design in React means geometry, primary colors, and zero decoration. Here's how to build components that honor the 100-year-old school's principles in modern UI.

Bold geometric shapes in primary red, blue, and yellow on a white background, evoking Bauhaus design principles

What Bauhaus Design Actually Means for UI

Honestly, most developers who say 'Bauhaus-inspired' just mean 'flat with some rectangles.' That's not it. The Bauhaus school — founded in Weimar, Germany in 1919 — was a philosophy: every design decision must serve the object's function. Decoration that doesn't work is decoration you remove.

In UI terms, this translates to a very specific visual vocabulary. Primary colors only — red, blue, yellow, black, white. Geometric shapes: circles, triangles, squares. Typography that is structural, not decorative. Grid systems where every element sits on a deliberate axis. No gradients unless a gradient communicates something.

This is fundamentally different from styles like glassmorphism, which leans on visual metaphor and depth effects. Bauhaus doesn't pretend the screen is glass or clay. It's a flat surface. Own it.

For a React component library, that means your component API reflects the visual constraints. No rounded prop that defaults to rounded-2xl. No shadow classes by default. Everything is intentional or it doesn't ship.

The Bauhaus Color System in Tailwind v4

Tailwind v4.0.2 ships with a full HSL-based color system, which is actually a bit at odds with Bauhaus purity. Bauhaus doesn't use HSL. It uses pigment logic: you start with three primaries and derive meaning from them. So you'll need to override the defaults.

Here's a practical Tailwind config for a Bauhaus token set. Define these in your CSS @theme block:

@theme {
  --color-bauhaus-red: #D62828;
  --color-bauhaus-blue: #003049;
  --color-bauhaus-yellow: #F77F00;
  --color-bauhaus-black: #0D0D0D;
  --color-bauhaus-white: #F5F0E8;
  --color-bauhaus-mid: #FCBF49;

  --font-display: 'DIN Condensed', 'Impact', sans-serif;
  --font-body: 'IBM Plex Mono', monospace;

  --spacing-grid: 8px;
}

That 8px grid unit matters. Every spacing value in a Bauhaus system is a multiple of 8: 16px, 24px, 32px, 64px. It's not optional — it's the structural logic that keeps the grid honest. In Tailwind you'll map this to gap-2 (8px), gap-4 (16px), gap-8 (32px), and so on with a base of 4px per unit. You might want to reconfigure spacing to use 8px as the base multiple to stay strict.

Don't use gray-* shades. If you need a neutral, mix bauhaus-black at reduced opacity: rgba(13, 13, 13, 0.15) for a disabled state, rgba(13, 13, 13, 0.4) for secondary text. That keeps you inside the system.

Building a Bauhaus Button Component in React

A Bauhaus button is a rectangle. Full stop. No border radius, no shadow, no gradient. Color carries the semantic weight — red for destructive, blue for primary, yellow for warning or accent. The text is uppercase, tracked out, and set in a geometric sans or condensed display face.

Here's a button component that holds to those rules:

import { cva, type VariantProps } from 'class-variance-authority';

const button = cva(
  [
    'inline-flex items-center justify-center',
    'uppercase tracking-widest text-sm font-bold',
    'border-2 border-[#0D0D0D]',
    'transition-none', // Bauhaus doesn't animate for decoration
    'focus-visible:outline-2 focus-visible:outline-offset-2',
    'focus-visible:outline-[#0D0D0D]',
    'disabled:opacity-40 disabled:cursor-not-allowed',
  ],
  {
    variants: {
      intent: {
        primary: 'bg-[#003049] text-[#F5F0E8] hover:bg-[#D62828]',
        destructive: 'bg-[#D62828] text-[#F5F0E8] hover:bg-[#0D0D0D]',
        accent: 'bg-[#F77F00] text-[#0D0D0D] hover:bg-[#FCBF49]',
        ghost: 'bg-transparent text-[#0D0D0D] hover:bg-[#0D0D0D] hover:text-[#F5F0E8]',
      },
      size: {
        sm: 'px-4 py-2 text-xs',
        md: 'px-6 py-3 text-sm',
        lg: 'px-8 py-4 text-base',
      },
    },
    defaultVariants: {
      intent: 'primary',
      size: 'md',
    },
  }
);

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
  VariantProps<typeof button>;

export function BauhausButton({ intent, size, className, ...props }: ButtonProps) {
  return (
    <button
      className={button({ intent, size, className })}
      {...props}
    />
  );
}

Notice there's no rounded class anywhere. No shadow-*. The hover state changes background color — that's the animation. That's all the animation you get. If you want to understand how this contrasts with frosted glass effects, read the glassmorphism vs neumorphism breakdown — it's a useful counterpoint.

Bauhaus Grid Layout and Structural Composition

Bauhaus layouts use asymmetric grids. Not 12-column equal-width grids — structural grids where proportions communicate hierarchy. A sidebar at 1/4 width, a content block at 3/4. Or a hero block at 2/3 with a contrasting panel at 1/3 in a different primary color.

In React with Tailwind, you'd model this with CSS Grid rather than Flexbox. Grid gives you explicit placement control, which Bauhaus demands. Here's a typical Bauhaus card layout:

export function BauhausCard({ title, category, body }: CardProps) {
  return (
    <div className="grid grid-cols-[1fr_3fr] border-2 border-[#0D0D0D]">
      {/* Color block — structural, not decorative */}
      <div className="bg-[#D62828] p-6 flex items-end">
        <span
          className="uppercase tracking-widest text-[#F5F0E8] text-xs font-bold"
          style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
        >
          {category}
        </span>
      </div>

      {/* Content block */}
      <div className="p-6 bg-[#F5F0E8] flex flex-col gap-4">
        <h2 className="text-2xl font-bold uppercase tracking-tight text-[#0D0D0D]">
          {title}
        </h2>
        <p className="text-sm leading-relaxed text-[#0D0D0D]/80 font-mono">
          {body}
        </p>
      </div>
    </div>
  );
}

The rotated vertical text on the color block is very Bauhaus — it's structural, it's typographic, and it earns its position by labeling the card category. That's form following function. The color block isn't there because it looks nice. It's there because it carries information.

Typography Rules: Geometric Sans and Structural Hierarchy

Herbert Bayer, one of the Bauhaus typography masters, proposed a universal typeface based purely on geometric construction in 1925. You're not going to replicate that exactly in a web app — but you can get close with the right font pairing and sizing scale.

For headings, use a condensed geometric sans: 'DIN Condensed', 'Bebas Neue', or 'Barlow Condensed'. Set it at tracking-tight or even negative letter-spacing for large display sizes. For body text, IBM Plex Mono or another monospaced face works well — it reinforces the structural, technical character of the Bauhaus aesthetic.

Your type scale should skip decorative sizes. Use 12, 14, 16, 24, 32, 48, 64 — multiples that relate to your 8px grid. In Tailwind these map neatly to text-xs, text-sm, text-base, text-2xl, text-3xl, text-5xl. Don't use text-xl or text-4xl in a strict Bauhaus system — those fall between the grid intervals.

Alignment is left or center — never justified. Bauhaus text doesn't stretch to fill columns. It sits on the left edge or on the axis of symmetry. Line height (leading-) should be leading-tight (1.25) for display text and leading-relaxed (1.625) for body. Those are the two modes. Nothing in between.

Bauhaus vs Other Minimalist UI Styles

Developers sometimes conflate Bauhaus with neobrutalism because both use hard edges and thick borders. They're different. Neobrutalism deliberately makes things feel broken or confrontational — it uses offset box-shadows, misaligned elements, and intentionally ugly typography choices. Bauhaus is rigorous, not rough.

And Bauhaus is definitely not neumorphism. Neumorphism uses soft shadows to create the illusion of physical buttons rising from a surface. That's precisely the kind of decorative illusionism Bauhaus rejects. In Bauhaus, a button is flat because a screen is flat.

What's the actual practical difference when you're choosing a style for a project? Bauhaus works best for data-heavy tools, dashboards, and technical applications where the user needs to process information fast. The geometric regularity reduces cognitive load. It's not that it looks serious — it's that it doesn't get in the way. If you're building something where personality and warmth matter, like a consumer app, you probably want claymorphism or a glassmorphism approach instead.

For toggling between these visual systems at runtime, adding a theme toggle in React is worth reading — the same component can present itself in Bauhaus mode or glassmorphism mode depending on a CSS class on the root element.

Accessibility in a High-Contrast Bauhaus System

Here's the thing: Bauhaus color combinations are naturally high-contrast. Black on white, white on deep blue, black on yellow — these all clear WCAG AA contrast ratios without any effort. That's the rare case where aesthetic choice and accessibility requirements point in the same direction.

Check your actual contrast values though, not just your intuition. #D62828 red on #F5F0E8 off-white gives a contrast ratio of approximately 4.1:1 — that passes AA for large text (3:1 required) but not AA for body text (4.5:1 required). For small text on red backgrounds, switch to white (#F5F0E8#FFFFFF) to push it past 4.5:1.

For focus rings, skip the browser default. Use a 2px solid #0D0D0D outline with a 2px offset — that's the Bauhaus-native focus style. It fits the aesthetic and it's unmissable. Add it to all interactive elements via Tailwind's focus-visible: variant, not focus: — you don't want the ring showing on mouse click. The button component above already does this.

Reduced motion is worth considering too. Bauhaus doesn't animate much, so this is easy: @media (prefers-reduced-motion: reduce) has almost nothing to turn off. The main thing to suppress would be any entrance animations on page load, if you added them.

Using Empire UI Bauhaus Components in Your Project

Empire UI ships a Bauhaus style preset that covers buttons, cards, form inputs, navigation, and data display components. All of them follow the rules described above — 8px grid, primary color palette, no border radius, geometric typography. You install it like any other Empire UI style variant.

The component API stays consistent across styles. If you're already using Empire UI components with a different visual system, you can switch to Bauhaus by changing the theme prop at the provider level. Your component JSX doesn't change. Only the visual output does. That architecture also makes it straightforward to compare Bauhaus against other approaches — it's worth reading Tailwind vs CSS Modules if you're deciding how to structure the styling layer underneath.

One practical note: Bauhaus components are deliberately opinionated. The border-radius is zero and it's not configurable. The colors are drawn from the primary set only. If a component you need doesn't fit those constraints — say, a toast notification that's supposed to feel friendly — you'll want to reach for a different Empire UI style for that component. Mix-and-match across a full app layout usually doesn't work. Commit to the system or don't.

FAQ

Can I use border radius at all in a Bauhaus React component?

Technically, very small radii (1-2px) are sometimes used in digital Bauhaus interpretations to avoid aliasing artifacts on screens. But the canonical answer is no — border-radius: 0 is part of the system. If you find yourself reaching for rounded corners, you're probably drifting toward a different style.

Which fonts work best for Bauhaus UI in a web app?

Bebas Neue and Barlow Condensed are solid free options for display headings. IBM Plex Mono works well for body text. If you have a budget, DIN Next is the closest digital equivalent to the original Bauhaus typographic aesthetic. Avoid humanist or oldstyle serifs — they contradict the geometric premise.

How do I handle dark mode in a Bauhaus design system?

Swap bauhaus-white (#F5F0E8) and bauhaus-black (#0D0D0D) as the background/foreground pair. Keep the primary colors (red, blue, yellow) identical — they read differently on a dark background but remain valid. Use Tailwind's dark: variant or a CSS class on <html> to switch token values. The geometric structure stays the same.

Is Bauhaus design suitable for e-commerce or consumer-facing apps?

It depends on what you're selling. For technical products, developer tools, or industrial goods, it's a strong fit. For fashion, food, or consumer lifestyle apps where warmth and approachability matter, it tends to read as cold. Most e-commerce teams mix Bauhaus structural principles with a softer color palette — which is a reasonable compromise, though not pure Bauhaus.

How does the 8px grid work in Tailwind without custom config?

Tailwind's default spacing scale uses 4px as the base unit (so gap-2 = 8px, gap-4 = 16px, gap-8 = 32px). You can stick to even-numbered spacing utilities (gap-2, gap-4, gap-6, gap-8) to stay on the 8px grid without any config changes. Just never use gap-1, gap-3, or gap-5 in a Bauhaus layout — those fall between grid intervals.

Why does Bauhaus UI use uppercase text so heavily?

Uppercase text in Bauhaus isn't a stylistic quirk — it's a structural choice. Uppercase letterforms have more consistent heights and widths, which makes them easier to align on a grid. They also read as labels rather than prose, which fits UI contexts where text is navigational or categorical rather than narrative. Use it for headings, button labels, category tags, and navigation items — not for body copy.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

Aurora UI Effects: Animated Northern Lights in CSS and ReactUI Design Predictions for 2027: Styles That Will Define the YearRTL Design System Support: Right-to-Left Layout in ReactCSS Variables as Design Tokens: The Complete Implementation