Skip to content
← Back to snippets
CSSTailwind CSS

CSS and Tailwind patterns: layout, type, effects

Twelve CSS and Tailwind recipes: fluid typography with clamp(), responsive grids without media queries, line clamping, skeleton loaders, custom scrollbars, gradient text, dark mode by class, and theme variables.

Most of these replace something that used to need a media query, a wrapper element, or JavaScript. That is the through-line: modern CSS has absorbed a lot of what used to be layout plumbing, and the remaining work is knowing which primitive to reach for.

Sizing without breakpoints

clamp(min, preferred, max) scales a value smoothly between two bounds, which removes the stepped feel of breakpoint-driven typography. The detail that matters is the middle argument: a pure vw value looks right until someone zooms or raises their default font size, at which point the text stops responding to either. Mixing a rem component with the vw component keeps the value tied to user preferences as well as viewport width — that is why the fluid type scale here is written as rem + vw rather than vw alone.

grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)) does the same job for layout. The grid reflows by itself at whatever width the content needs, with no breakpoints to maintain and no numbers to update when the card design changes.

Truncation has two different answers

Single-line truncation is text-overflow: ellipsis with white-space: nowrap and overflow: hidden — all three, or it does nothing. Multi-line truncation is -webkit-line-clamp, which despite the prefix is now specified and supported across current browsers. They are not variants of one technique; the single-line version cannot clamp to three lines, and the clamp version needs display: -webkit-box to work at all.

Skeletons and scrollbars are about perceived speed

A skeleton loader does not make anything faster, it makes the wait legible — the layout stops jumping when content arrives because the space was already reserved. Worth pairing with prefers-reduced-motion, since a shimmering animation is exactly what that setting exists to suppress.

Custom scrollbars now have two syntaxes: the older ::-webkit-scrollbar pseudo-elements and the standard scrollbar-width / scrollbar-color properties. Writing both is not redundancy — coverage differs by browser, and the standard properties are the ones with a future.

Tailwind: theme variables and the dark-mode strategy

Tailwind's class strategy for dark mode means a .dark class on <html> drives everything, which is what makes a manual toggle possible at all — the media-query strategy follows the system and cannot be overridden by the user. Pairing it with CSS custom properties for the palette keeps the two themes in one place instead of scattering dark: variants across every component.

CSSCSS

Center anything with flexbox

A reusable utility class that perfectly centers a single child both horizontally and vertically.

How it works: display: flex combined with align-items: center and justify-content: center centers the flex item on both axes regardless of its size.

.center {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100%;
}
<div class="center">
  <div class="card">I'm centered</div>
</div>
CSSCSS

Custom CSS Scrollbar

Styles the scrollbar to match a design system without JavaScript — thin track, rounded thumb, hover state. Includes a variant to hide the scrollbar while keeping scroll behaviour.

How it works: ::-webkit-scrollbar-* pseudo-elements work in Chrome, Safari, Edge, and Opera. Firefox uses the standard scrollbar-color / scrollbar-width. Both approaches are needed for full coverage.

/* Custom scrollbar (Chromium + Safari + Edge) */
.custom-scrollbar {
  scrollbar-width: thin;                  /* Firefox */
  scrollbar-color: #9ca3af transparent;   /* Firefox: thumb track */
}

.custom-scrollbar::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

.custom-scrollbar::-webkit-scrollbar-track {
  background: transparent;
}

.custom-scrollbar::-webkit-scrollbar-thumb {
  background-color: #9ca3af;
  border-radius: 999px;
}

.custom-scrollbar::-webkit-scrollbar-thumb:hover {
  background-color: #6b7280;
}

/* Hide scrollbar but keep scroll behaviour */
.hide-scrollbar {
  scrollbar-width: none;         /* Firefox */
  -ms-overflow-style: none;      /* IE / Edge legacy */
}

.hide-scrollbar::-webkit-scrollbar {
  display: none;
}
<!-- Example usage -->
<div class="custom-scrollbar" style="height: 300px; overflow-y: auto;">
  <!-- long content -->
</div>
CSSCSS

Fluid Typography with clamp()

Scales font size smoothly between a minimum and maximum value based on viewport width — no media queries, just clamp().

How it works: clamp(min, preferred, max) clamps a value between minimum and maximum. The middle parameter is a linear function of the viewport width (vw). Formula: preferred = minFont + (maxFont - minFont) * (vw - minVw) / (maxVw - minVw).

/* Fluid typography utilities */
:root {
  /* clamp(min-size, fluid-value, max-size) */
  --text-sm:   clamp(0.75rem,  0.7rem + 0.25vw,  0.875rem);
  --text-base: clamp(0.875rem, 0.8rem + 0.4vw,   1rem);
  --text-lg:   clamp(1rem,     0.9rem + 0.5vw,    1.125rem);
  --text-xl:   clamp(1.125rem, 1rem + 0.6vw,      1.25rem);
  --text-2xl:  clamp(1.25rem,  1rem + 1.25vw,     1.5rem);
  --text-3xl:  clamp(1.5rem,   1rem + 2.5vw,      2.25rem);
  --text-4xl:  clamp(2rem,     1.5rem + 2.5vw,    3rem);
  --text-5xl:  clamp(2.5rem,   2rem + 2.5vw,      4rem);
}

h1 { font-size: var(--text-5xl); }
h2 { font-size: var(--text-4xl); }
h3 { font-size: var(--text-3xl); }
p  { font-size: var(--text-base); }

/* Or inline */
.hero-title {
  font-size: clamp(2rem, 5vw + 1rem, 5rem);
  line-height: 1.1;
}

.body-text {
  font-size: clamp(1rem, 0.5vw + 0.875rem, 1.125rem);
  line-height: 1.6;
}
CSSCSS

CSS Gradient Text

Applies a linear or radial gradient to text via background-clip: text — no SVG, no JavaScript. Supported by all modern browsers.

How it works: a gradient is set as the background on the element, then background-clip: text clips the background to the character outlines, and color: transparent makes the text itself see-through — revealing the gradient background underneath.

/* Basic horizontal gradient */
.gradient-text {
  background: linear-gradient(90deg, #f97316, #ec4899);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Diagonal with three stops */
.gradient-text--diagonal {
  background: linear-gradient(135deg, #6366f1 0%, #a855f7 50%, #ec4899 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* Animated (moving gradient) */
.gradient-text--animated {
  background: linear-gradient(90deg, #f97316, #ec4899, #6366f1, #f97316);
  background-size: 300% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: gradient-shift 4s linear infinite;
}

@keyframes gradient-shift {
  0%   { background-position: 0% center; }
  100% { background-position: 300% center; }
}
<!-- Example usage -->
<h1 class="gradient-text">Hello, world</h1>
<h2 class="gradient-text--animated">Animated Gradient</h2>
CSSCSS

CSS Grid auto-fit — Responsive Grid Without Media Queries

Creates a self-adapting column grid with auto-fit and minmax — cards reflow automatically as the container width changes, with zero breakpoints.

How it works: repeat(auto-fit, minmax(min, 1fr)) makes CSS Grid add and remove columns automatically. auto-fit collapses empty tracks; auto-fill preserves them. minmax(240px, 1fr) means each column is at least 240px and at most an equal share of free space.

/* Basic responsive grid */
.auto-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1.5rem;
}

/* Variant using min() to avoid overflow on very small screens */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  gap: 1.5rem;
}

/* Photo gallery: equal square cells */
.photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  grid-auto-rows: 160px;
  gap: 4px;
}

.photo-grid img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
<!-- Example usage -->
<div class="auto-grid">
  <div class="card">Card 1</div>
  <div class="card">Card 2</div>
  <div class="card">Card 3</div>
  <!-- wraps automatically — no media queries needed -->
</div>
CSSCSS

Multiline text-overflow with Ellipsis

Truncates text after N lines with an ellipsis via -webkit-line-clamp — works in all modern browsers without JavaScript.

How it works: -webkit-line-clamp combined with display: -webkit-box and -webkit-box-orient: vertical limits the block to the given number of lines. Despite the -webkit- prefix, it's supported by all modern browsers including Firefox.

/* Single line */
.truncate-1 {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* N lines (change --lines) */
.line-clamp {
  --lines: 3;
  display: -webkit-box;
  -webkit-line-clamp: var(--lines);
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* Ready-to-use utilities */
.line-clamp-2 { --lines: 2; }
.line-clamp-3 { --lines: 3; }
.line-clamp-4 { --lines: 4; }
<!-- Example usage -->
<p class="line-clamp" style="--lines: 2">
  A very long piece of text that will be clipped after the second line
  with an ellipsis at the end, no JavaScript required...
</p>
CSSCSS

Skeleton Loading Animation (CSS)

A shimmer effect for loading skeletons — a pulsing gradient simulates content loading. No JavaScript, pure CSS animation using custom properties.

How it works: @keyframes shimmer moves a gradient from left to right across the element. Using background-size: 200% lets the gradient overflow the edges — creating a sweeping light effect. will-change: background-position helps GPU acceleration.

/* Base skeleton block */
.skeleton {
  background: linear-gradient(
    90deg,
    var(--skeleton-base, #e5e7eb) 25%,
    var(--skeleton-shine, #f3f4f6) 50%,
    var(--skeleton-base, #e5e7eb) 75%
  );
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: 4px;
}

@keyframes shimmer {
  0%   { background-position: 200% center; }
  100% { background-position: -200% center; }
}

/* Dark theme */
@media (prefers-color-scheme: dark) {
  .skeleton {
    --skeleton-base: #374151;
    --skeleton-shine: #4b5563;
  }
}

/* Preset shapes */
.skeleton-text   { height: 0.875rem; border-radius: 2px; }
.skeleton-title  { height: 1.25rem;  border-radius: 2px; }
.skeleton-avatar { width: 40px; height: 40px; border-radius: 50%; }
.skeleton-card   { height: 120px; border-radius: 8px; }
<!-- Example usage — card in loading state -->
<div style="display: flex; gap: 12px; align-items: center">
  <div class="skeleton skeleton-avatar"></div>
  <div style="flex: 1; display: flex; flex-direction: column; gap: 8px">
    <div class="skeleton skeleton-title" style="width: 60%"></div>
    <div class="skeleton skeleton-text" style="width: 90%"></div>
    <div class="skeleton skeleton-text" style="width: 75%"></div>
  </div>
</div>
CSSCSS

Truncate single-line text with an ellipsis

Cuts off overflowing text on one line and adds "…" at the end — the classic CSS-only text truncation.

How it works: this only works for a SINGLE line — white-space: nowrap prevents wrapping, overflow: hidden clips it, and text-overflow: ellipsis swaps the clipped part for "…". For multiple lines, use line-clamp instead.

.truncate {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
<p class="truncate" style="max-width: 200px;">
  This sentence is way too long to fit on one line and will be truncated
</p>
Tailwind CSS

Tailwind CSS animations — keyframes and transition

Craft custom animations with Tailwind's `animate-*` utilities and `@keyframes` — from micro-interactions to full page transitions — without leaving your config.

How it works: define keyframes and animation utilities in tailwind.config.js. Each key gets an animate-{name} class. Combine with transition-*, hover:, and group-hover: for interactive micro-animations.

// tailwind.config.js
export default {
  theme: {
    extend: {
      keyframes: {
        'fade-in': {
          '0%': { opacity: '0', transform: 'translateY(10px)' },
          '100%': { opacity: '1', transform: 'translateY(0)' },
        },
        'scale-in': {
          '0%': { transform: 'scale(0.95)', opacity: '0' },
          '100%': { transform: 'scale(1)', opacity: '1' },
        },
        'slide-up': {
          '0%': { transform: 'translateY(100%)' },
          '100%': { transform: 'translateY(0)' },
        },
        'pulse-soft': {
          '0%, 100%': { opacity: '1' },
          '50%': { opacity: '0.5' },
        },
      },
      animation: {
        'fade-in': 'fade-in 0.3s ease-out',
        'scale-in': 'scale-in 0.2s ease-out',
        'slide-up': 'slide-up 0.4s ease-out',
        'pulse-soft': 'pulse-soft 2s ease-in-out infinite',
      },
    },
  },
}
<!-- Use animation classes -->
<div class="animate-fade-in">
  <h1>Fades in with slide</h1>
</div>

<div class="animate-scale-in">Modal backdrop</div>

<!-- Stagger children -->
<div class="space-y-4">
  <div class="animate-fade-in" style="animation-delay: 0ms">Item 1</div>
  <div class="animate-fade-in" style="animation-delay: 100ms">Item 2</div>
  <div class="animate-fade-in" style="animation-delay: 200ms">Item 3</div>
</div>

<!-- Hover micro-interactions -->
<button class="transition-all duration-200 hover:scale-105 hover:shadow-lg active:scale-95">
  Hover me
</button>

<!-- Group hover reveal -->
<div class="group relative">
  <p>Hover this area</p>
  <div class="opacity-0 group-hover:opacity-100 transition-opacity duration-200">
    Revealed content
  </div>
</div>
Tailwind CSS

Tailwind CSS custom theme with CSS variables

Extend Tailwind's default theme with custom colors, fonts, spacing, and breakpoints — all driven by CSS variables for runtime theme switching.

How it works: define CSS variables in @layer base and reference them in tailwind.config.js via the theme.extend block. This keeps design tokens in one place and enables runtime theme switching (just swap variable values).

/* globals.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --color-primary: #2563eb;
    --color-primary-light: #60a5fa;
    --color-secondary: #7c3aed;
    --color-accent: #f59e0b;
    --color-surface: #ffffff;
    --color-surface-muted: #f8fafc;
    --color-border: #e2e8f0;
    --font-sans: 'Inter', system-ui, sans-serif;
    --font-display: 'Playfair Display', serif;
    --spacing-page: 2rem;
  }

  .dark {
    --color-primary: #60a5fa;
    --color-primary-light: #93c5fd;
    --color-surface: #0f172a;
    --color-surface-muted: #1e293b;
    --color-border: #334155;
  }
}
// tailwind.config.js
export default {
  theme: {
    extend: {
      colors: {
        primary: {
          DEFAULT: 'var(--color-primary)',
          light: 'var(--color-primary-light)',
        },
        secondary: 'var(--color-secondary)',
        accent: 'var(--color-accent)',
        surface: {
          DEFAULT: 'var(--color-surface)',
          muted: 'var(--color-surface-muted)',
        },
      },
      fontFamily: {
        sans: ['var(--font-sans)'],
        display: ['var(--font-display)'],
      },
      spacing: {
        page: 'var(--spacing-page)',
      },
    },
  },
}
<!-- Usage in templates -->
<div class="bg-surface text-primary font-sans">
  <h1 class="font-display text-3xl">Custom Theme</h1>
</div>
Tailwind CSS

Tailwind CSS dark mode with class strategy

Toggle dark mode on demand instead of relying on system preferences — the class strategy lets users switch themes with a button, persisted in localStorage or cookie.

How it works: set darkMode: "class" in tailwind.config.js and toggle a dark class on <html>. All dark: variants activate when the parent element has the class. This gives you full control over the theme — useful for user-facing theme toggles.

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
export default {
  darkMode: 'class',
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
}
<!-- Dark mode classes -->
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
  <h1 class="text-gray-900 dark:text-white">Title</h1>
  <p class="text-gray-600 dark:text-gray-400">Content with adaptive colors</p>
  <button class="bg-blue-600 dark:bg-blue-500 text-white px-4 py-2 rounded-lg
                 hover:bg-blue-700 dark:hover:bg-blue-400 transition-colors">
    Submit
  </button>
</div>

<!-- Toggle button -->
<button onclick="toggleDarkMode()">
  <span class="dark:hidden">☀️ Light</span>
  <span class="hidden dark:inline">🌙 Dark</span>
</button>
// Theme toggle with localStorage persistence
function toggleDarkMode() {
  const html = document.documentElement
  const isDark = html.classList.toggle('dark')
  localStorage.setItem('theme', isDark ? 'dark' : 'light')
}

// On page load
const saved = localStorage.getItem('theme')
if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
  document.documentElement.classList.add('dark')
}
Tailwind CSS

Tailwind CSS responsive card grid

A responsive card grid with Tailwind CSS that adapts from 1 column on mobile to 4 columns on desktop — no media queries needed.

How it works: Tailwind's responsive grid utilities (grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4) generate the breakpoints declaratively. Cards use gap for spacing, aspect-video for consistent media, and truncate for text overflow.

<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 p-6">
  <div v-for="item in items" :key="item.id"
       class="bg-white dark:bg-gray-800 rounded-xl shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-md transition-shadow">
    <div class="aspect-video bg-gray-100 dark:bg-gray-700">
      <img :src="item.image" :alt="item.title" class="w-full h-full object-cover">
    </div>
    <div class="p-4 space-y-2">
      <span class="text-xs font-medium text-blue-600 dark:text-blue-400 uppercase tracking-wide">
        {{ item.category }}
      </span>
      <h3 class="font-semibold text-gray-900 dark:text-white truncate">
        {{ item.title }}
      </h3>
      <p class="text-sm text-gray-500 dark:text-gray-400 line-clamp-2">
        {{ item.description }}
      </p>
    </div>
  </div>
</div>
/* Optional: keep line-clamp utility if Tailwind v3 */
.line-clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
# Tailwind v4 uses line-clamp via @utility or just
# use the native CSS above
// CONNECT

Contact Information

Submit a request or write to me directly on Telegram. I'll reply within 24 hours.

Provide either your email or Telegram so I can reply.