Container Component: Inspired from Shadcn and Tailwind UI

I've been working with shadcn/ui for a while now, and there's something that always felt missing—a proper container component. You know, the kind that handles responsive layouts elegantly without you having to think about it every time.

The Problem I Kept Running Into

Every project needs containers. Whether you're building a landing page or a complex dashboard, you're constantly wrapping content in divs with max-w-7xl mx-auto px-4 sm:px-6 lg:px-8. It works, but it's repetitive and error-prone. I found myself copying the same patterns from Tailwind UI examples, tweaking padding and max-widths project after project.

Learning from Tailwind UI's Approach

Tailwind UI has this really thoughtful approach to containers. They don't just slap a container class on everything. Instead, they use specific patterns:

  • Full-width on mobile, constrained above
  • Always constrained with consistent padding
  • Breakpoint-specific constraints
  • Narrow layouts for content-heavy pages

Each pattern serves a real purpose. The "full-width on mobile" approach, for example, maximizes screen real estate on small devices while providing comfortable reading widths on larger screens.

Building the Component

I experimented with class-variance-authority to handle the different layout patterns cleanly. The key insight was that each variant needs its own padding strategy—some start padding at sm:, others include mobile padding from the start.

Installation

Option 1: Install via Registry (Recommended)

bunx shadcn@latest add https://registry.emreturan.dev/r/container.json

This will automatically install the component and its dependencies to your project.

Option 2: Manual Installation

Copy and paste the following code into your project:

import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"

const containerVariants = cva("mx-auto", {
  variants: {
    variant: {
      fullMobileConstrainedPadded: "max-w-7xl sm:px-6 lg:px-8",
      constrainedPadded: "max-w-7xl px-4 sm:px-6 lg:px-8",
      fullMobileConstrainedBreakpointPadded: "max-w-screen-xl sm:px-6 lg:px-8",
      constrainedBreakpointPadded: "max-w-screen-xl px-4 sm:px-6 lg:px-8",
      narrowConstrainedPadded: "max-w-3xl px-4 sm:px-6 lg:px-8",
    },
  },
  defaultVariants: {
    variant: "narrowConstrainedPadded",
  },
})

export interface ContainerProps extends React.HTMLAttributes<HTMLDivElement>, VariantProps<typeof containerVariants> {}

const Container = React.forwardRef<HTMLDivElement, ContainerProps>(
  ({ className, children, variant, ...props }, ref) => {
    return (
      <div ref={ref} className={cn(containerVariants({ variant }), className)} {...props}>
        {children}
      </div>
    )
  },
)

Container.displayName = "Container"

export { Container, containerVariants }

Usage Examples

Here's how I typically use it:

// For blog posts and articles
<Container variant="narrowConstrainedPadded">
  <article>Your content here</article>
</Container>

// For full-width mobile, constrained desktop
<Container variant="fullMobileConstrainedPadded">
  <header>Your header content</header>
</Container>

Each variant serves a specific layout need. The naming follows Tailwind UI's conventions—verbose but descriptive. Once you use them a few times, the patterns stick.

The cn Dependency

This component needs the cn utility function from shadcn/ui for class merging. If you're already using shadcn/ui, you're set. If not, you can either grab their cn implementation or use something like clsx with tailwind-merge.

The cn function handles the tricky business of merging Tailwind classes properly—something that's surprisingly complex when you need to override conflicting utilities.

What's Next

This component fills a gap I kept running into. Tailwind UI has these container patterns, shadcn/ui has the component philosophy, but nobody had bridged them into a reusable component.

If you end up using this, I'd love to hear how it works in your projects. The beauty of this approach is that it's yours to modify—no package dependencies, no version conflicts, just code you control.