Skip to main content

Tailwind CSS v4

What is Tailwind CSS?

Tailwind CSS is a utility-first CSS framework that provides low-level utility classes to build custom designs directly in your markup. Rather than pre-designed components, Tailwind gives you the building blocks to create any design, quickly and efficiently.

Version 4 represents a major evolution with significant improvements:

  • Performance: Lightning-fast builds with the new Oxide engine
  • CSS-first configuration: Native CSS for theme customization using @theme
  • Smaller bundle sizes: Optimized output for production
  • Better developer experience: Improved IntelliSense and error messages
  • Native cascade layers: Better control over style precedence

Why Together Agency Uses Tailwind CSS

At Together Agency, we've adopted Tailwind CSS as our primary styling solution for several key reasons:

  • Consistency: Utility classes enforce design system constraints, ensuring visual consistency across projects
  • Speed: Rapidly prototype and build production-ready interfaces without writing custom CSS
  • Maintainability: No CSS file bloat—styles live alongside components, making refactoring easier
  • Responsive Design: Built-in responsive modifiers make mobile-first design straightforward
  • Developer Experience: Excellent IDE support with IntelliSense for class names
  • Scalability: Design tokens and utility classes scale seamlessly from small sites to large applications
  • Integration: Works seamlessly with our Next.js and React-based workflow

For more information about Tailwind CSS, visit the official documentation.

Setting Up Tailwind CSS v4 in Next.js

This guide covers how to set up Tailwind CSS v4 in a Next.js project. For the most up-to-date instructions, refer to the official Next.js installation guide.

1. Create Your Next.js Project

If you're starting fresh, create a new Next.js project:

npx create-next-app@latest my-project --typescript --eslint --app
cd my-project

2. Install Tailwind CSS

Install Tailwind CSS v4 and its peer dependencies:

npm install tailwindcss @tailwindcss/postcss postcss autoprefixer

3. Configure PostCSS

Create a postcss.config.mjs file in the root of your project and add the @tailwindcss/postcss plugin:

postcss.config.mjs
const config = {
plugins: ["@tailwindcss/postcss", "autoprefixer"],
};
export default config;

4. Import Tailwind CSS

Add an @import statement to your main CSS file (typically ./app/globals.css) that imports Tailwind CSS:

app/globals.css
@import "tailwindcss";
tip

In Tailwind CSS v4, you no longer need a tailwind.config.js file for basic setup. Theme customization is done directly in CSS using the @theme directive.

5. Start Your Development Server

Run your build process:

npm run dev

6. Start Using Tailwind

You can now use Tailwind's utility classes in your components:

app/page.tsx
export default function Home() {
return <h1 className="text-3xl font-bold underline">Hello world!</h1>;
}

Essential Plugins

Together Agency uses several official and community plugins to extend Tailwind's functionality. Here are the three key plugins we rely on:

@tailwindcss/typography

The typography plugin provides beautiful typographic defaults for content-heavy pages, particularly useful for blog posts, documentation, and CMS content.

Installation:

npm install @tailwindcss/typography

Usage in CSS:

app/globals.css
@import "tailwindcss";
@plugin "@tailwindcss/typography";

Usage in Components:

<article className="prose lg:prose-xl">
<h1>Article Title</h1>
<p>Beautiful, accessible typography with sensible defaults...</p>
</article>

The prose class automatically styles all child elements (headings, paragraphs, lists, links, etc.) with appropriate spacing, sizing, and styling. You can customize these styles in your CSS, as demonstrated in our Typography System section.

@tailwindcss/forms

The forms plugin provides a basic reset for form elements, making them easier to style consistently across browsers.

Installation:

npm install @tailwindcss/forms

Usage in CSS:

app/globals.css
@import "tailwindcss";
@plugin "@tailwindcss/forms";

Benefits:

  • Normalizes form elements across browsers
  • Provides better default styling for inputs, selects, and textareas
  • Makes forms easier to customize with Tailwind utilities
  • Includes dark mode support out of the box

Example:

<input
type="email"
className="rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
placeholder="[email protected]"
/>

tailwind-clamp

The tailwind-clamp plugin enables fluid responsive typography using CSS clamp() function, allowing text to scale smoothly between breakpoints without multiple responsive classes.

Installation:

npm install tailwind-clamp

Usage in CSS:

app/globals.css
@import "tailwindcss";
@plugin "tailwind-clamp";

Usage in Components:

// Traditional approach (multiple breakpoints)
<h1 className="text-2xl md:text-4xl lg:text-5xl">Heading</h1>

// Clamp approach (fluid scaling)
<h1 className="clamp-[text,2rem,5rem]">Heading</h1>

The syntax is clamp-[property,min,max] where:

  • property: The CSS property to clamp (text for font-size)
  • min: Minimum value (at smallest viewport)
  • max: Maximum value (at largest viewport)

This results in fluid typography that scales proportionally between the min and max values, creating a smoother responsive experience. Together Agency uses this extensively—see our Typography System for real-world examples.

Overriding Variables & Theme Customization

Tailwind CSS v4 introduces a new approach to theme customization using the @theme directive. Instead of a JavaScript configuration file, you define your design tokens directly in CSS, providing better performance and integration with your styles.

The @theme Directive

The @theme directive allows you to define custom CSS variables that Tailwind will recognize and use to generate utility classes.

Basic Example:

app/globals.css
@import "tailwindcss";

@theme {
--color-primary: #3b82f6;
--color-secondary: #8b5cf6;
--font-heading: "Inter", sans-serif;
--font-body: "Open Sans", sans-serif;
--radius-large: 1rem;
}

Now you can use these tokens with Tailwind utilities:

<button className="bg-primary text-white rounded-large font-heading">
Click me
</button>

Custom Colors

Define your brand colors in a @theme block:

@theme {
--color-brand-pink: #ff83da;
--color-brand-yellow: #e0fe2c;
--color-brand-charcoal: #2d2c37;
--color-neutral-grey: #f4f5f9;
--color-neutral-white: #ffffff;
}

Use them with standard Tailwind color utilities:

<div className="bg-brand-pink text-neutral-white">
Styled with custom colors
</div>

Custom Breakpoints

Add custom breakpoints for responsive design:

@theme {
--breakpoint-xs: 24.5625rem; /* 393px */
--breakpoint-2xs: 20rem; /* 320px */
--breakpoint-mdx: 55rem; /* 880px */
}

Custom Easing Functions

Define custom timing functions for animations:

@theme {
--ease-custom: cubic-bezier(0.8, 0, 0.2, 1);
--ease-smooth: cubic-bezier(0.6, 0, 0.1, 1);
}

Use with transition utilities:

<div className="transition-all duration-300 ease-custom">Smooth animation</div>

Custom Spacing and Sizing

Define custom spacing values:

@theme {
--radius-10: 0.625rem; /* 10px */
--spacing-section: 5rem;
}
tip

Keep all your theme customizations in one place (or organized across separate CSS files) to maintain a single source of truth for your design system.

Best Practices

Here are some best practices we follow at Together Agency when working with Tailwind CSS:

Use Semantic Color Names

Instead of specific color names, use semantic naming that describes the color's purpose:

/* ✅ Good - Semantic naming */
@theme {
--color-primary: #3b82f6;
--color-accent: #f59e0b;
--color-surface: #f9fafb;
}

/* ❌ Avoid - Specific color names */
@theme {
--color-blue-500: #3b82f6;
--color-orange-400: #f59e0b;
--color-gray-50: #f9fafb;
}

This makes it easier to update your color scheme without changing class names throughout your codebase.

Leverage CSS Layers

Use @layer to organize your custom styles and control specificity:

@import "tailwindcss";

@layer components {
.btn {
@apply rounded-lg px-4 py-2 font-semibold transition-colors;
}

.btn-primary {
@apply bg-primary text-white hover:bg-primary-dark;
}
}

@layer utilities {
.text-balance {
text-wrap: balance;
}
}

Layers ensure your styles integrate properly with Tailwind's cascade and can be purged correctly in production.

Create Reusable Component Classes

For frequently used component patterns, create semantic classes using @apply:

@layer components {
.card {
@apply rounded-lg border border-gray-200 bg-white p-6 shadow-sm;
}

.section-padding {
@apply py-16 md:py-24 lg:py-32;
}
}

Use Arbitrary Values Sparingly

While Tailwind supports arbitrary values (text-[14px], bg-[#1da1f2]), prefer defined tokens:

/* ✅ Good - Use design tokens */
<div className="text-lg bg-primary">

/* ⚠️ Use sparingly - Arbitrary values */
<div className="text-[17px] bg-[#3b82f6]">

Arbitrary values should only be used for truly one-off cases where defining a token doesn't make sense.

Mobile-First Responsive Design

Always write mobile styles first, then use breakpoint modifiers for larger screens:

/* ✅ Good - Mobile-first */
<div className="text-base md:text-lg lg:text-xl">

/* ❌ Avoid - Desktop-first requires more classes */
<div className="text-xl lg:text-lg md:text-base">

Organize Classes Logically

Keep class names organized for readability:

/* ✅ Good - Organized by category */
<button className="
flex items-center gap-2
rounded-lg px-4 py-2
bg-primary text-white
transition-colors hover:bg-primary-dark
focus:outline-none focus:ring-2 focus:ring-primary
">

/* ❌ Avoid - Random order */
<button className="text-white px-4 hover:bg-primary-dark gap-2 flex rounded-lg py-2 bg-primary items-center focus:outline-none transition-colors">

Consider this order: layout → spacing → typography → colors → effects → interactions.

Performance Considerations

  • Purge unused styles: Tailwind automatically purges unused classes in production
  • Avoid excessive arbitrary values: They can't be purged as effectively
  • Use component classes for complex patterns: Reduces markup size and improves maintainability
  • Leverage CSS layers: Helps with specificity and purging

Together Agency Implementation

At Together Agency, we've developed a structured approach to organizing Tailwind CSS in production projects. This section demonstrates real-world patterns we use across client sites, providing a scalable foundation that maintains consistency while allowing flexibility.

Similar to how we approach Atomic Design for components, our CSS architecture follows a modular pattern where small, focused files combine to create a comprehensive design system.

File Structure Pattern

We organize our styles into separate, focused CSS files that are imported into a main entry point. This modular approach makes it easier to maintain, scale, and collaborate on styles across teams.

Typical structure:

app/
└── styles/
├── globals.css # Main entry point with imports and base config
├── colors.css # Color design tokens in @theme
├── typography.css # Text styles and heading classes
├── spacings.css # Custom spacing utilities
├── containers.css # Container and scroll utilities
├── forms.css # Form input styling (HubSpot integration)
├── utils.css # Miscellaneous utility classes
└── swiper.css # Third-party library style overrides

Benefits of this structure:

  • Separation of concerns: Each file has a clear, specific purpose
  • Easier collaboration: Team members can work on different files simultaneously
  • Better maintainability: Finding and updating styles is straightforward
  • Scalability: Easy to add new files as the project grows
  • Reusability: Files can be copied between projects and adapted
tip

This structure mirrors the component-based architecture of modern React applications. Just as you wouldn't put all components in one file, don't put all styles in one file either.

Main Entry Point (globals.css)

The globals.css file serves as the main entry point that orchestrates all other style files. It imports Tailwind, registers plugins, imports custom styles with appropriate layers, defines theme variables, and creates custom utilities.

Full example:

app/styles/globals.css
@import "tailwindcss";

@plugin "@tailwindcss/typography";
@plugin "@tailwindcss/forms";
@plugin "tailwind-clamp";

@import "./colors.css" layer(components);
@import "./spacings.css" layer(components);
@import "./utils.css" layer(components);
@import "./containers.css" layer(components);
@import "./forms.css" layer(components);
@import "./typography.css" layer(utilities);
@import "./swiper.css" layer(utilities);

@source not inline("container");

@theme {
--font-soehne: var(--font-soehne);

--breakpoint-2xs: 20rem; /* 320px */
--breakpoint-xs: 24.5625rem; /* 393px */
--breakpoint-mdx: 55rem; /* 880px */

--ease-mews: cubic-bezier(0.8, 0, 0.2, 1);
--ease-mews-out: cubic-bezier(0.6, 0, 0.1, 1);

--radius-10: 0.625rem; /* 10px */
}

html {
@apply scroll-smooth;
scroll-padding-top: 96px;
}

@utility mask-to-b {
mask: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.8) 50%,
rgba(0, 0, 0, 1) 100%
);
}

@utility mask-to-t {
mask: linear-gradient(
to top,
rgba(0, 0, 0, 0) 0%,
rgba(0, 0, 0, 0.8) 50%,
rgba(0, 0, 0, 1) 100%
);
}

@utility border-curved-left {
@apply absolute inset-y-0 left-0 z-10 h-full w-1.5 rounded-l-10 border border-r-0 border-current;
}

@utility border-curved-right {
@apply absolute inset-y-0 right-0 z-10 h-full w-1.5 rounded-r-10 border border-l-0 border-current;
}

@utility border-curved-top {
@apply absolute inset-x-0 top-0 z-10 h-1.5 w-full rounded-t-10 border border-b-0 border-current;
}

@utility border-curved-bottom {
@apply absolute inset-x-0 bottom-0 z-10 h-1.5 w-full rounded-b-10 border border-t-0 border-current;
}

/* Autofill styling */
input:-webkit-autofill,
input:-webkit-autofill:focus {
transition: background-color 600000s 0s, color 600000s 0s;
}

input[data-autocompleted] {
background-color: transparent !important;
}

Key elements explained:

  1. Import Tailwind: The @import "tailwindcss" statement loads Tailwind's base styles
  2. Register plugins: @plugin directives load official and community plugins
  3. Layer imports: Each custom CSS file is imported with a specific layer (components or utilities)
  4. Theme variables: The @theme block defines custom design tokens
  5. Global styles: HTML element styles that apply site-wide
  6. Custom utilities: The @utility directive creates reusable utility classes
  7. Browser fixes: Styles to handle browser-specific issues (like autofill)

The @source directive:

@source not inline("container");

This tells Tailwind not to inline the container class, which is useful when you have custom container definitions.

Layer strategy:

  • layer(components): For classes that combine multiple utilities into reusable patterns
  • layer(utilities): For single-purpose utility classes

This ensures proper CSS cascade order: base → components → utilities.

Color System (colors.css)

We define all brand and supporting colors in a dedicated colors.css file using the @theme directive. This creates a single source of truth for the color palette and makes it easy to use colors consistently across the entire application.

Example:

app/styles/colors.css
@theme {
--color-pink: #ff83da;
--color-pink-light: #ffc5ee;
--color-pink-lightest: #f7e1f7;
--color-grey: #f4f5f9;
--color-charcoal: #2d2c37;
--color-black-2: #161616;
--color-black-1: #1c1b1c;
--color-black: #000000;
--color-white: #ffffff;
--color-yellow: #e0fe2c;
--color-yellow-light: #effd91;
--color-orange: #ff5303;
--color-green: #d1f9d6;
--color-blue: #d2f4ff;
--color-cream: #fffcf6;
--color-gold-lightest: #fbf8ec;
--color-supporting-grey: #c4c9dd;
--color-faded-grey: #d6d9e7;
}

Usage in components:

Once defined, these colors are automatically available as Tailwind utilities:

// Background colors
<div className="bg-pink">
<div className="bg-grey">
<div className="bg-charcoal">

// Text colors
<p className="text-pink">
<h1 className="text-charcoal">
<span className="text-yellow">

// Border colors
<button className="border border-pink">
<div className="border-t-2 border-supporting-grey">

// With opacity modifiers
<div className="bg-pink/50"> {/* 50% opacity */}
<div className="text-black/80"> {/* 80% opacity */}

Color naming strategy:

  • Brand colors: Primary brand colors with descriptive names (pink, yellow, orange)
  • Color variants: Lighter/darker shades with suffixes (pink-light, pink-lightest)
  • Neutral colors: Standard neutrals (black, white, grey)
  • Numbered variants: When multiple shades of the same hue exist (black-1, black-2)
  • Supporting colors: Utility colors for specific use cases (supporting-grey, faded-grey)
tip

Keep color names semantic to the brand, not generic. Using --color-pink is better than --color-primary when pink is part of the brand identity, as it's more descriptive and easier for designers and developers to understand.

Typography System (typography.css)

Typography is one of the most important aspects of any design system. We create a comprehensive set of typography classes that provide consistency and make it easy to implement designs pixel-perfect from Figma.

Key principles:

  • Use semantic class names (.text-h1, .text-body-m) instead of Tailwind's default size classes
  • Leverage clamp() for fluid responsive typography
  • Define styles for both standard text and rich text content from CMS
  • Include specialized classes for stats, captions, and UI elements

Example heading and body text classes:

app/styles/typography.css
html {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@apply font-soehne;
}

/* Heading styles */
.text-h1 {
@apply clamp-[text,2.5rem,4.5rem] leading-[1.03] font-black tracking-[-0.02em];
}

.text-h2 {
@apply clamp-[text,2rem,3.375rem] leading-[1.08] font-bold tracking-[-0.03em];
}

.text-h3 {
@apply clamp-[text,1.75rem,3rem] leading-[1.1] font-bold tracking-[-0.015em];
}

.text-h4 {
@apply clamp-[text,1.5rem,2.5rem] leading-[1.1] font-bold tracking-[-0.025em];
}

/* Body text styles */
.text-body-l {
@apply clamp-[text,1.125rem,1.25rem] leading-normal font-normal;
}

.prose-lg,
.text-body-m {
@apply clamp-[text,1rem,1.125rem] leading-normal font-normal tracking-[0.01em];
}

.text-body-s {
@apply clamp-[text,0.875rem,1rem] leading-normal font-normal tracking-[0.01em];
}

/* Caption styles */
.text-caps-s {
@apply clamp-[text,0.6875rem,0.8125rem] leading-[1.5] font-bold tracking-[0.03em] uppercase;
}

Usage in components:

<h1 className="text-h1 text-charcoal">
Large Heading
</h1>

<h2 className="text-h2 text-black">
Section Heading
</h2>

<p className="text-body-m text-charcoal/80">
Body text content that's easy to read.
</p>

<span className="text-caps-s text-pink">
Small Caption
</span>

Specialized typography classes:

For specific use cases, we create additional typography utilities:

app/styles/typography.css
/* Stat/number displays */
.stat-100 {
@apply clamp-[text,4rem,6.25rem] leading-[.8] font-black;
}

.stat-80 {
@apply clamp-[text,2rem,5rem] leading-[.8] font-black tracking-[-0.02em] uppercase;
}

/* Fixed-size utilities for specific use cases */
.text-24 {
@apply clamp-[text,1.25rem,1.5rem] leading-[1.35] font-bold tracking-[-0.01em];
}

.text-96 {
@apply clamp-[text,2.5rem,6rem] leading-[1.1] font-black tracking-[-0.02em] uppercase md:leading-[1.03];
}

Rich text content styling:

For content coming from a CMS (like blog posts or documentation), we override the @tailwindcss/typography plugin's defaults:

app/styles/typography.css
.prose {
max-width: 100%;
}

/* Article heading overrides */
.rich-text-article
:where(h2):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
@apply mt-10 mb-3 clamp-[text,2rem,2.25rem] leading-[1.25] font-bold tracking-[-0.02em] md:mt-[52px];
}

.rich-text-article
:where(h3):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
@apply clamp-[text,1.75rem,2rem] leading-[1.1] font-bold tracking-[-0.02em];
}

/* Paragraph styling */
.rich-text-article
:where(p):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
@apply mt-3 mb-6 leading-[1.7] tracking-[0.01em] first:mt-0;
}

/* Link styling */
.payload-richtext
:where(a):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
@apply underline decoration-pink underline-offset-2;
}

/* List styling */
.payload-richtext
:where(ul):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
@apply !list-outside !list-disc !pl-0 marker:text-pink;
}

.payload-richtext
:where(ul > li):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
@apply !mb-3 !ml-[18px] !pl-0 last:!mb-0 md:!mb-[17px];
}

Why clamp() for typography:

Using clamp() creates fluid typography that scales smoothly between viewport sizes:

// Traditional approach - sudden jumps at breakpoints
<h1 className="text-2xl md:text-4xl lg:text-6xl">

// Clamp approach - smooth scaling
<h1 className="text-h1"> {/* clamp-[text,2.5rem,4.5rem] */}

The clamp approach:

  • Creates smoother responsive typography
  • Reduces the number of breakpoint-specific classes
  • Matches design tools like Figma that often show fluid scaling
  • Improves readability across all device sizes
tip

Don't use Tailwind's default text size classes (text-xl, text-2xl, etc.) in production code. They make it harder to match designs and don't provide the fluid responsiveness that clamp() offers. Instead, create semantic classes like .text-h1 that map to your design system.

Spacing System (spacings.css)

Consistent spacing is crucial for visual harmony. We create custom spacing utilities that match our design system and provide responsive behavior out of the box.

Example spacing utilities:

app/styles/spacings.css
/* Padding top utilities */
.padding-t-48 {
@apply pt-12;
}

.padding-t-64 {
@apply pt-12 md:pt-16;
}

.padding-t-96 {
@apply pt-16 md:pt-24;
}

.padding-t-128 {
@apply pt-16 md:pt-32;
}

/* Padding bottom utilities */
.padding-b-48 {
@apply pb-12;
}

.padding-b-64 {
@apply pb-12 md:pb-16;
}

.padding-b-96 {
@apply pb-16 md:pb-24;
}

.padding-b-128 {
@apply pb-16 md:pb-32;
}

/* Vertical spacing utilities */
.space-40 {
@apply space-y-10;
}

.space-48 {
@apply space-y-10 md:space-y-12;
}

.space-80 {
@apply space-y-10 md:space-y-20;
}

.space-128 {
@apply space-y-12 md:space-y-32;
}

Usage in components:

{
/* Section with responsive padding */
}
<section className="padding-t-96 padding-b-128">
<div className="container">
<h2>Section Heading</h2>
</div>
</section>;

{
/* Stack with consistent spacing */
}
<div className="space-80">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</div>;

Why custom spacing utilities:

  1. Design system alignment: Numbers match your design specifications (48, 64, 96, 128)
  2. Built-in responsiveness: Mobile and desktop spacing defined once
  3. Semantic naming: Clear what the spacing value represents
  4. Consistency: Prevents arbitrary spacing values throughout the codebase

Naming convention:

  • padding-t-{value}: Padding top
  • padding-b-{value}: Padding bottom
  • space-{value}: Vertical spacing between children (uses space-y)
  • Value represents the desktop pixel value (e.g., padding-t-96 = 96px padding on desktop)

Container System (containers.css)

Container utilities control max-width, padding, and responsive behavior for content sections. We define custom container classes that match our layout specifications.

Example container utilities:

app/styles/containers.css
.container {
max-width: 1278px !important;
box-sizing: content-box;
@apply mx-auto w-auto px-5 lg:px-8;
}

.container-1360 {
max-width: 1360px !important;
box-sizing: content-box;
@apply mx-auto w-auto px-5 lg:px-8;
}

.container .container {
padding-left: 0 !important;
padding-right: 0 !important;
}

/* Mobile overflow scroll containers */
.overflow-scroll-container-md {
@apply -mx-5 max-md:snap-x max-md:snap-mandatory max-md:overflow-x-auto md:mx-0;
}

.overflow-scroll-item-md {
@apply ml-5 last:mr-5 max-md:shrink-0 max-md:snap-center md:ml-0 md:last:mr-0;
}

.overflow-scroll-container-lg {
@apply -mx-5 max-lg:snap-x max-lg:snap-mandatory max-lg:overflow-x-auto lg:mx-0;
}

.overflow-scroll-item-lg {
@apply last:mr-5 max-lg:ml-5 max-lg:shrink-0 max-lg:snap-center;
}

Standard container usage:

<section>
<div className="container">
<h1>Content within standard container</h1>
<p>Max-width of 1278px, responsive padding</p>
</div>
</section>;

{
/* Wider container variant */
}
<section>
<div className="container-1360">
<h1>Content within wider container</h1>
</div>
</section>;

Overflow scroll patterns:

For mobile-friendly horizontal scrolling (like card carousels):

<div className="container">
<div className="overflow-scroll-container-md">
<div className="flex gap-4">
<div className="overflow-scroll-item-md w-[280px]">Card 1</div>
<div className="overflow-scroll-item-md w-[280px]">Card 2</div>
<div className="overflow-scroll-item-md w-[280px]">Card 3</div>
<div className="overflow-scroll-item-md w-[280px]">Card 4</div>
</div>
</div>
</div>

On mobile, this creates a swipeable horizontal scroll with snap points. On tablet and above (md breakpoint), it displays as a normal flex layout.

Container benefits:

  • Consistent max-width: All content sections have standardized maximum widths
  • Responsive padding: Automatically adjusts padding for different screen sizes
  • Nested container handling: Prevents double padding when containers are nested
  • Scroll utilities: Mobile-first patterns for horizontal scrolling content
tip

The negative margin trick (-mx-5) in overflow scroll containers allows full-bleed scrolling on mobile while maintaining consistent padding with the rest of the page content.

Form Styling (forms.css)

Many of our projects integrate with HubSpot for forms and email marketing. We create custom form styles that integrate seamlessly with HubSpot's markup while maintaining brand consistency.

Example form utilities:

app/styles/forms.css
.hs-input {
@apply mb-4 block w-full rounded-md border-0 bg-white px-5 py-3 text-[1rem] leading-[1.35] tracking-[0.01rem] text-black/80 ring-1 ring-black/10 ring-inset placeholder:text-black/45 focus:ring-black/20 focus:outline-none md:h-12;
width: 100% !important;
}

.hs-button {
@apply h-11 w-full rounded-full bg-pink px-5;
}

.hs-submit {
@apply pt-3;
}

.hs-richtext {
@apply mb-4 text-black/65;
}

.hs-richtext a {
@apply text-black underline;
text-decoration-color: #ff83da;
}

/* Hide HubSpot reCAPTCHA badge */
.hs_recaptcha {
@apply hidden;
}

/* Custom range input styling */
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
cursor: pointer;
border: 0.375rem solid;
@apply size-5 rounded-full border-white bg-supporting-grey drop-shadow-md;
}

input[type="range"] {
-webkit-appearance: none;
background: #ff83da;
background-image: linear-gradient(#ff83da, #ff83da);
background-repeat: no-repeat;
}

input[type="range"]::-webkit-slider-runnable-track {
-webkit-appearance: none;
box-shadow: none;
border: none;
background: transparent !important;
}

Inline form pattern:

For newsletter signup forms embedded in content:

app/styles/forms.css
.rich-text_hubspot-subscribe form {
@apply grid gap-3 sm:inline-flex sm:items-center sm:justify-center sm:space-x-3 sm:rounded-full sm:border sm:border-black/10 sm:bg-white sm:p-[0.375rem] sm:pl-5;
}

.rich-text_hubspot-subscribe .hs-input {
box-shadow: none;
@apply m-0 sm:h-10 sm:border-0 sm:p-0;
}

.rich-text_hubspot-subscribe .hs-submit {
@apply m-0 p-0;
}

Usage:

HubSpot forms automatically receive these styles when embedded:

<div className="rich-text_hubspot-subscribe">
{/* HubSpot form embed script */}
</div>

Form styling benefits:

  • Consistent design: All forms match the brand aesthetic automatically
  • HubSpot integration: Styles work with HubSpot's generated markup
  • Accessibility: Proper focus states and ARIA support
  • Responsive: Forms adapt to different screen sizes
  • Custom inputs: Styled range inputs, phone inputs, and more

Utility Classes (utils.css)

The utilities file contains miscellaneous utility classes for common patterns that don't fit into other categories.

Example utility classes:

app/styles/utils.css
/* Hide scrollbars */
.hide-scrollbars {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
}

.hide-scrollbars::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}

/* Button base styles */
.btn {
@apply inline-flex cursor-pointer items-center justify-center gap-x-2.5 rounded whitespace-nowrap transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50;
}

/* Vertical mask gradient */
.vert-mask {
-webkit-mask: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
currentColor 32px,
currentColor calc(100% - 32px),
rgba(0, 0, 0, 0) 100%
);
mask: linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0%,
currentColor 32px,
currentColor calc(100% - 32px),
rgba(0, 0, 0, 0) 100%
);
}

/* Horizontal mask gradient */
.horizontal-mask {
-webkit-mask: linear-gradient(
to right,
rgba(0, 0, 0, 0) 0%,
currentColor 32px,
currentColor calc(100% - 32px),
rgba(0, 0, 0, 0) 100%
);
mask: linear-gradient(
to right,
rgba(0, 0, 0, 0) 0%,
currentColor 32px,
currentColor calc(100% - 32px),
rgba(0, 0, 0, 0) 100%
);
}

/* Triangle shape for tooltips/dropdowns */
.rounded-triangle {
display: block;
height: 13px;
width: 13px;
background-color: inherit;
border: inherit;
position: absolute;
bottom: -6px;
left: calc(50% - 6px);
clip-path: polygon(0% 0%, 100% 100%, 0% 100%);
transform: rotate(-45deg);
border-radius: 0 0 0 0.25em;
}

/* Video embed wrapper */
.video-embed-wrapper iframe {
@apply absolute inset-0 !h-full !w-full;
}

Usage examples:

{
/* Hide scrollbar on container */
}
<div className="overflow-auto hide-scrollbars">{/* Scrollable content */}</div>;

{
/* Button with base styles */
}
<button className="btn bg-pink text-white px-6 py-3">Click me</button>;

{
/* Vertical fade effect on scrollable list */
}
<div className="vert-mask h-[400px] overflow-y-auto">
{/* Long list of items */}
</div>;

{
/* Responsive video embed */
}
<div className="video-embed-wrapper relative aspect-video">
<iframe src="..." />
</div>;

Third-Party Library Overrides (swiper.css)

When using third-party libraries like Swiper.js, we create dedicated style overrides to match our design system.

Example Swiper customization:

app/styles/swiper.css
.swiper-pagination {
@apply pointer-events-none flex items-center gap-x-2.5;
height: 11px;
}

.swiper-pagination-bullet {
@apply pointer-events-auto;
}

/* White theme variant */
.swiper-theme-white {
--swiper-theme-color: white;
}

.swiper-theme-white .swiper-pagination-bullet {
background: white !important;
margin: 0 !important;
@apply transition-[height,opacity] duration-300 hover:!opacity-30;
}

.swiper-theme-white .swiper-pagination-bullet-active {
background: white !important;
width: 8px !important;
height: 11px !important;
}

/* Custom progress bar */
.custom-progress-bar {
@apply !relative !h-px overflow-hidden !bg-white/20;

.swiper-pagination-progressbar-fill {
@apply !bg-pink;
}
}

/* Mixed scale animation */
.mixed-scale-swiper .swiper-slide {
@apply scale-100 transition-transform duration-500 ease-mews;
}

.mixed-scale-swiper .mixed-scale-image {
@apply origin-bottom-left scale-10 transition-transform duration-500 ease-mews;
}

.mixed-scale-swiper .swiper-slide-visible .mixed-scale-image {
@apply scale-100;
}

/* Specific slider positioning */
.swiper-customers-hero .swiper-pagination {
@apply !bottom-[2.875rem] justify-end px-7 lg:!bottom-16;
}

Usage in components:

import { Swiper, SwiperSlide } from "swiper/react";
import "swiper/css";

<Swiper className="swiper-theme-white">
<SwiperSlide>Slide 1</SwiperSlide>
<SwiperSlide>Slide 2</SwiperSlide>
<SwiperSlide>Slide 3</SwiperSlide>
</Swiper>;

Third-party styling benefits:

  • Design consistency: Library components match your brand
  • Separation of concerns: Overrides are isolated in their own file
  • Maintainability: Easy to update when upgrading libraries
  • Theme variants: Multiple theme options for different contexts
tip

When styling third-party libraries, always create a dedicated CSS file rather than scattering overrides throughout your codebase. This makes it much easier to maintain and update when the library changes.

Key Patterns & Conventions

Based on our production experience, here's a summary of the key patterns and conventions we follow at Together Agency when working with Tailwind CSS v4:

CSS Organization

  • Use @layer for all custom styles: Organize CSS into components and utilities layers to control cascade order
  • One file, one purpose: Keep CSS files focused on a single concern (colors, typography, spacing, etc.)
  • Import with layers: Always specify the layer when importing custom CSS files

Design Tokens

  • Define colors in @theme: All color values should be defined once in a theme block, never hardcoded
  • Semantic naming: Use names that describe purpose, not appearance (avoid blue-500, prefer primary)
  • Document values: Add comments for pixel equivalents and usage notes

Typography

  • Create semantic classes: Use .text-h1, .text-body-m instead of Tailwind's default size utilities
  • Leverage clamp() for fluidity: Enable smooth responsive typography with the tailwind-clamp plugin
  • Override prose styles: Customize @tailwindcss/typography output to match your design system
  • No default size classes: Avoid text-xl, text-2xl in production—create semantic alternatives

Spacing

  • Custom utilities for common patterns: Create .padding-t-96, .space-80 for design system values
  • Mobile-first responsiveness: Build responsive behavior into utility classes
  • Pixel-based naming: Use desktop pixel values in class names for clarity

Custom Utilities

  • Use @utility directive: Create custom utilities with the @utility directive in v4
  • Combine utilities with @apply: Build component classes from Tailwind utilities
  • Prefix third-party overrides: Make it clear which styles target external libraries

Component Classes

  • Extract repeated patterns: If you use the same combination of utilities 3+ times, extract to a component class
  • Layer correctly: Component classes go in layer(components), utilities in layer(utilities)
  • Document usage: Add comments explaining when and how to use custom classes

Maintainability

  • Keep overrides isolated: Third-party library styles in dedicated files (e.g., swiper.css)
  • Comment complex styles: Explain "why" for non-obvious CSS
  • Version control design tokens: Track changes to colors, spacing, typography in git
  • Test across browsers: Especially Safari for SVGs, masks, and advanced CSS features

Performance

  • Minimize arbitrary values: They increase bundle size and can't be purged efficiently
  • Use utility classes in markup: Avoid creating wrapper divs just to apply styles
  • Leverage Tailwind's JIT: The just-in-time compiler generates only the CSS you use

Official Documentation

Official Plugins

Community Plugins

  • Atomic Design: Our component architecture methodology
  • Code Practices: Best practices for React and TypeScript in our projects

Tools & Resources

  • Tailwind CSS IntelliSense: VSCode extension for class name autocomplete
  • Tailwind CSS Color Shades Generator: Generate color palettes for your design system
  • Can I Use: Check browser support for CSS features used in Tailwind

By following these patterns and leveraging Tailwind CSS v4's powerful features, we create maintainable, scalable, and performant styling systems that serve our clients well long after projects launch.