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:
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:
@import "tailwindcss";
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:
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:
@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:
@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:
@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:
@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;
}
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
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:
@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:
- Import Tailwind: The
@import "tailwindcss"statement loads Tailwind's base styles - Register plugins:
@plugindirectives load official and community plugins - Layer imports: Each custom CSS file is imported with a specific layer (components or utilities)
- Theme variables: The
@themeblock defines custom design tokens - Global styles: HTML element styles that apply site-wide
- Custom utilities: The
@utilitydirective creates reusable utility classes - 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 patternslayer(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:
@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)
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:
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:
/* 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:
.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
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:
/* 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:
- Design system alignment: Numbers match your design specifications (48, 64, 96, 128)
- Built-in responsiveness: Mobile and desktop spacing defined once
- Semantic naming: Clear what the spacing value represents
- Consistency: Prevents arbitrary spacing values throughout the codebase
Naming convention:
padding-t-{value}: Padding toppadding-b-{value}: Padding bottomspace-{value}: Vertical spacing between children (usesspace-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:
.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
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:
.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:
.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:
/* 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:
.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
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
@layerfor all custom styles: Organize CSS intocomponentsandutilitieslayers 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, preferprimary) - Document values: Add comments for pixel equivalents and usage notes
Typography
- Create semantic classes: Use
.text-h1,.text-body-minstead of Tailwind's default size utilities - Leverage
clamp()for fluidity: Enable smooth responsive typography with thetailwind-clampplugin - Override prose styles: Customize
@tailwindcss/typographyoutput to match your design system - No default size classes: Avoid
text-xl,text-2xlin production—create semantic alternatives
Spacing
- Custom utilities for common patterns: Create
.padding-t-96,.space-80for 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
@utilitydirective: Create custom utilities with the@utilitydirective 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 inlayer(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
Resources & Links
Official Documentation
- Tailwind CSS v4 Documentation: https://tailwindcss.com/
- Next.js Installation Guide: https://tailwindcss.com/docs/installation/framework-guides/nextjs
- Tailwind CSS GitHub: https://github.com/tailwindlabs/tailwindcss
Official Plugins
- @tailwindcss/typography: https://github.com/tailwindlabs/tailwindcss-typography
- @tailwindcss/forms: https://github.com/tailwindlabs/tailwindcss-forms
Community Plugins
- tailwind-clamp: https://github.com/sambauers/tailwindcss-3d
Related Together Agency Documentation
- 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.