Menu

Tailwind CSS Ring Width

Ring Width defines the thickness of an outline or border that circles around an element, often used for focus states or visual emphasis. It is created using box shadow. Tailwind allows you to add and style ring widths effortlessly, hiding complexity to streamline your development workflow.

This guide will cover everything from adding basic ring widths and customizing focus rings to applying states, responsive breakpoints, and even custom values. By the end, you'll have a comprehensive understanding of leveraging Tailwind CSS utilities to create visually appealing and functional designs.

ClassPropertiesExample
ring-0box-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color);<div className="ring-0"></div>
ring-1box-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);<div className="ring-1"></div>
ring-2box-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);<div className="ring-2"></div>
ringbox-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);<div className="ring"></div>
ring-4box-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);<div className="ring-4"></div>
ring-8box-shadow: var(--tw-ring-inset) 0 0 0 calc(8px + var(--tw-ring-offset-width)) var(--tw-ring-color);<div className="ring-8"></div>
ring-inset--tw-ring-inset: inset;<div className="ring-inset"></div>

Overview of Ring Width

Adding the Ring Width

The simplest way to incorporate ring-width in Tailwind is by using pre-designed classes to outline an element consistently. By default, the ring-* utility applies an aesthetically balanced width and offers immediate application. Let's look at this setup:

This is a live editor. Play around with it!
export default function BasicRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-gray-100">
      <img
        src="https://images.unsplash.com/photo-1527443224154-c4a3942d3acf"
        alt="Beautiful landscape"
        className="ring-4 ring-blue-500 rounded-lg"
      />
    </div>
  );
}

// Classes applied:
// .ring-4: Applies a 4px ring width
// .ring-blue-500: Outlines with a blue color from Tailwind's palette
// .rounded-lg: Soft corner radius for an elegant look

Here, the image is outlined with a 4px solid blue ring, complemented by rounded corners for a polished visual effect. Tailwind provides several predefined widths that you can experiment with based on your layout.

Adding the Ring on Focus

Focus ring widths are particularly critical for accessibility, signaling interactive focus on elements like buttons, inputs, or links. Tailwind seamlessly supports these states using default focus utilities.

This is a live editor. Play around with it!
export default function FocusRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-gray-900">
      <button className="ring-2 ring-yellow-400 focus:ring-4 focus:ring-yellow-500 focus:outline-none px-4 py-2 bg-gray-800 text-white rounded-lg">
        Focus Me!
      </button>
    </div>
  );
}

// Classes Explained:
// .ring-2: Sets a default 2px ring width
// .focus:ring-4: Expands the ring width during focus states
// .focus:outline-none: Prevents default browser outline to maintain consistency

In this setup, the focus state utilizes a brighter highlight with an expanded 4px width, ensuring the user easily spots which element is in focus while adhering to accessibility best practices.

Adding the Inset Rings

Sometimes, you may want your ring to appear inside the element's padding instead of the outside boundary. Tailwind offers an inset modifier precisely for this purpose.

In the below example, the red ring is rendered inside of the button:

This is a live editor. Play around with it!
export default function InsetRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-gray-50">
      <button className="ring-red-500 ring-inset ring-4 border-4 border-dashed border-blue-500 p-4 rounded-lg">Click Here</button>
    </div>
  );
}

// Explanation:
// .ring-inset: Moves the visual boundary of the ring inward

This approach is highly effective for emphasizing circular or compact elements like user avatars or buttons with inner outlines.

States and Responsiveness

Hover and Focus States

Tailwind lets you define ring states for different interactive events, such as hover or focus, using its intuitive state utilities (hover:, focus:). These classes dynamically adjust ring widths based on user behavior, significantly improving UX.

This is a live editor. Play around with it!
export default function HoverFocusRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-gray-800">
      <button className="ring-2 ring-purple-600 hover:ring-4 hover:ring-purple-400 focus:ring-8 focus:ring-purple-300 text-white px-5 py-3 bg-purple-700 rounded-md">
        Interact With Me
      </button>
    </div>
  );
}

// Behavior Summary:
// .ring-2: Initial width for a general state
// .hover:ring-4: Increases outline width during hover
// .focus:ring-8: Sets a larger width when the user focuses on the button

Upon hovering or focusing, the ring progressively thickens, delivering real-time user feedback through rich visuals.

Breakpoint Modifiers

When designing adaptive UIs, you might want to adjust ring widths based on screen sizes. Tailwind's built-in breakpoints (sm:, md:, lg:, and beyond) help you create dynamic, responsive outlines effortlessly.

This is a live editor. Play around with it!
export default function ResponsiveRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-gray-100">
      <img
        src="https://images.unsplash.com/photo-1527443224154-c4a3942d3acf"
        className="ring-2 sm:ring-4 md:ring-6 lg:ring-8 xl:ring-10 ring-pink-500 max-w-xs rounded-md"
        alt="Responsive avatar"
      />
    </div>
  );
}

// Adaptive Scaling:
// .sm:ring-4: Ring scales up when viewport size reaches `sm` breakpoint
// Similarly, widths increase progressively through `md`, `lg`, and `xl` breakpoints

Custom Ring Width

Extending the Theme

Tailwind's configuration file (tailwind.config.js) allows you to customize ring utilities beyond predefined widths. You can achieve this by extending the theme property.

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

export default function CustomThemeRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-neutral-200">
      <div className="ring-3.5 ring-indigo-600 rounded-lg p-8 shadow-lg">
        Custom Widths Applied
      </div>
    </div>
  );
}

// Explanation:
// .ring-3.5: Applies the newly custom-defined `3.5px` width

With theme.extend, you're essentially expanding Tailwind to accommodate unique measurement requirements, ensuring harmony between design guidelines and practical needs.

Using Arbitrary Values

For cases where the predefined or configured widths don’t fit your exact requirements, Tailwind provides support for arbitrary values. This feature allows you to specify custom pixel values directly within your utility classes.

This is a live editor. Play around with it!
export default function ArbitraryRingWidth() {
  return (
    <div className="flex h-screen w-screen items-center justify-center bg-gray-100">
      <button
        className="focus:ring-[10px] focus:ring-sky-600 rounded-full p-4 text-white bg-sky-800"
      >
        Arbitrary Width
      </button>
    </div>
  );
}

// Notes:
// .focus:ring-[10px]: Dynamically alters the width to 10px during focus

This is especially valuable when you require granular control over component width without introducing new configurations or overriding core utilities.

Real World Examples

Product Card Grid with Focus States

This example shows a grid of product cards with different ring widths on hover and focus states, perfect for e-commerce interfaces.

This is a live editor. Play around with it!
export default function ProductGrid() {
  const products = [
    {
      id: 1,
      name: "Premium Leather Wallet",
      price: "$79.99",
      src: "https://images.unsplash.com/photo-1627123424574-724758594e93",
      alt: "Brown leather wallet"
    },
    {
      id: 2,
      name: "Wireless Headphones",
      price: "$199.99",
      src: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e",
      alt: "Black wireless headphones"
    },
    {
      id: 3,
      name: "Smart Watch",
      price: "$299.99",
      src: "https://images.unsplash.com/photo-1523275335684-37898b6baf30",
      alt: "Modern smartwatch"
    },
    {
      id: 4,
      name: "Laptop Backpack",
      price: "$89.99",
      src: "https://images.unsplash.com/photo-1553062407-98eeb64c6a62",
      alt: "Gray laptop backpack"
    },
    {
      id: 5,
      name: "Mechanical Keyboard",
      price: "$159.99",
      src: "https://images.unsplash.com/photo-1618384887929-16ec33fab9ef",
      alt: "RGB mechanical keyboard"
    },
    {
      id: 6,
      name: "Wireless Mouse",
      price: "$49.99",
      src: "https://images.unsplash.com/photo-1527864550417-7fd91fc51a46",
      alt: "Modern wireless mouse"
    }
  ];

  return (
    <div className="grid gap-6 p-8">
      {products.map((product) => (
        <div
          key={product.id}
          className="p-4 bg-white rounded-lg shadow-md transition-all hover:ring-4 hover:ring-blue-200 focus:ring-8 focus:ring-blue-300"
        >
          <img
            src={product.src}
            alt={product.alt}
            className="w-full h-48 object-cover rounded-md"
          />
          <h3 className="mt-4 text-lg font-semibold">{product.name}</h3>
          <p className="text-gray-600">{product.price}</p>
        </div>
      ))}
    </div>
  );
}

Interactive Profile Cards

This example demonstrates profile cards with different ring widths based on selection state.

This is a live editor. Play around with it!
export default function ProfileCards() {
  const profiles = [
    {
      id: 1,
      name: "Sarah Johnson",
      role: "UX Designer",
      src: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
      alt: "Sarah profile picture",
      selected: true
    },
    {
      id: 2,
      name: "Michael Chen",
      role: "Frontend Developer",
      src: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
      alt: "Michael profile picture",
      selected: false
    },
    {
      id: 3,
      name: "Emma Wilson",
      role: "Product Manager",
      src: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
      alt: "Emma profile picture",
      selected: false
    },
    {
      id: 4,
      name: "James Rodriguez",
      role: "Backend Developer",
      src: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e",
      alt: "James profile picture",
      selected: false
    },
    {
      id: 5,
      name: "Lisa Chang",
      role: "Data Analyst",
      src: "https://images.unsplash.com/photo-1517841905240-472988babdf9",
      alt: "Lisa profile picture",
      selected: false
    },
    {
      id: 6,
      name: "David Kim",
      role: "DevOps Engineer",
      src: "https://images.unsplash.com/photo-1463453091185-61582044d556",
      alt: "David profile picture",
      selected: false
    }
  ];

  return (
    <div className="grid gap-8 p-10">
      {profiles.map((profile) => (
        <div
          key={profile.id}
          className={`flex items-center p-6 bg-white rounded-xl shadow-sm transition-all cursor-pointer
            ${profile.selected ? 'ring-4 ring-purple-400' : 'hover:ring-2 hover:ring-purple-200'}`}
        >
          <img
            src={profile.src}
            alt={profile.alt}
            className="w-20 h-20 rounded-full object-cover"
          />
          <div className="ml-6">
            <h3 className="text-xl font-semibold">{profile.name}</h3>
            <p className="text-gray-600">{profile.role}</p>
          </div>
        </div>
      ))}
    </div>
  );
}

Feature Comparison Cards

This example shows feature comparison cards with ring width emphasis on hover.

This is a live editor. Play around with it!
export default function FeatureComparison() {
  const features = [
    {
      id: 1,
      title: "Basic Plan",
      price: "$9.99/mo",
      features: ["2GB Storage", "2 Users", "Basic Support"],
      recommended: false
    },
    {
      id: 2,
      title: "Pro Plan",
      price: "$19.99/mo",
      features: ["10GB Storage", "5 Users", "Priority Support"],
      recommended: true
    },
    {
      id: 3,
      title: "Team Plan",
      price: "$49.99/mo",
      features: ["50GB Storage", "10 Users", "24/7 Support"],
      recommended: false
    },
  ];

  return (
    <div className="grid gap-6 p-8">
      {features.map((plan) => (
        <div
          key={plan.id}
          className={`p-6 bg-white rounded-lg transition-all hover:ring-2 hover:ring-indigo-300
            ${plan.recommended ? 'ring-4 ring-indigo-500' : ''}`}
        >
          <h3 className="text-xl font-bold">{plan.title}</h3>
          <p className="text-2xl font-semibold mt-2">{plan.price}</p>
          <ul className="mt-4 space-y-2">
            {plan.features.map((feature, index) => (
              <li key={index} className="flex items-center">
                <span className="w-2 h-2 bg-indigo-500 rounded-full mr-2"></span>
                {feature}
              </li>
            ))}
          </ul>
        </div>
      ))}
    </div>
  );
}

Notification Cards

This example demonstrates notification cards with ring width indicators for different priority levels.

This is a live editor. Play around with it!
export default function NotificationCenter() {
  const notifications = [
    {
      id: 1,
      title: "System Update",
      message: "Critical security update available",
      priority: "high",
      time: "2 minutes ago"
    },
    {
      id: 2,
      title: "New Message",
      message: "You have a new message from Sarah",
      priority: "medium",
      time: "5 minutes ago"
    },
    {
      id: 3,
      title: "Backup Complete",
      message: "Weekly backup completed successfully",
      priority: "low",
      time: "10 minutes ago"
    },
    {
      id: 4,
      title: "Server Alert",
      message: "High CPU usage detected",
      priority: "high",
      time: "15 minutes ago"
    },
    {
      id: 5,
      title: "Task Completed",
      message: "Project deployment successful",
      priority: "medium",
      time: "20 minutes ago"
    },
    {
      id: 6,
      title: "Calendar",
      message: "Meeting reminder: Team sync at 2 PM",
      priority: "low",
      time: "25 minutes ago"
    }
  ];

  const getPriorityRing = (priority) => {
    switch(priority) {
      case 'high':
        return 'ring-4 ring-red-400';
      case 'medium':
        return 'ring-2 ring-yellow-400';
      default:
        return 'ring-1 ring-gray-200';
    }
  };

  return (
    <div className="space-y-4 p-6">
      {notifications.map((notification) => (
        <div
          key={notification.id}
          className={`p-4 bg-white rounded-lg shadow-sm ${getPriorityRing(notification.priority)}`}
        >
          <div className="flex justify-between items-start">
            <div>
              <h3 className="font-semibold">{notification.title}</h3>
              <p className="text-gray-600">{notification.message}</p>
            </div>
            <span className="text-sm text-gray-400">{notification.time}</span>
          </div>
        </div>
      ))}
    </div>
  );
}

Input Form Fields

This example shows form inputs with different ring widths for various states.

This is a live editor. Play around with it!
export default function FormFields() {
  const fields = [
    {
      id: 1,
      label: "Full Name",
      type: "text",
      placeholder: "Enter your full name",
      required: true,
      error: false
    },
    {
      id: 2,
      label: "Email",
      type: "email",
      placeholder: "Enter your email",
      required: true,
      error: true
    },
    {
      id: 3,
      label: "Phone Number",
      type: "tel",
      placeholder: "Enter your phone number",
      required: false,
      error: false
    },
    {
      id: 4,
      label: "Password",
      type: "password",
      placeholder: "Enter your password",
      required: true,
      error: false
    },
    {
      id: 5,
      label: "Address",
      type: "text",
      placeholder: "Enter your address",
      required: true,
      error: false
    },
    {
      id: 6,
      label: "Company",
      type: "text",
      placeholder: "Enter your company name",
      required: false,
      error: false
    }
  ];

  return (
    <form className="max-w-2xl mx-auto p-8 space-y-6">
      {fields.map((field) => (
        <div key={field.id} className="space-y-2">
          <label className="block text-sm font-medium text-gray-700">
            {field.label}
            {field.required && <span className="text-red-500">*</span>}
          </label>
          <input
            type={field.type}
            placeholder={field.placeholder}
            className={`w-full px-4 py-2 rounded-md border
              focus:outline-none focus:ring-2 focus:ring-blue-500
              ${field.error ? 'ring-2 ring-red-500' : 'hover:ring-1 hover:ring-gray-300'}`}
          />
          {field.error && (
            <p className="text-sm text-red-500">This field is required</p>
          )}
        </div>
      ))}
    </form>
  );
}

Customization Examples

Custom Ring Width for Profile Cards

This example demonstrates how to create profile cards with custom ring widths for different user status levels.

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

// App.js
export default function ProfileCards() {
  const profiles = [
    {
      name: "Sarah Johnson",
      status: "Premium Member",
      image: "https://images.unsplash.com/photo-1494790108377-be9c29b29330",
      ringStyle: "ring-premium ring-yellow-400"
    },
    {
      name: "Mike Chen",
      status: "Verified User",
      image: "https://images.unsplash.com/photo-1500648767791-00dcc994a43e",
      ringStyle: "ring-verified ring-blue-500"
    },
    {
      name: "Emma Wilson",
      status: "Standard User",
      image: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80",
      ringStyle: "ring-standard ring-gray-300"
    }
  ];

  return (
    <div className="flex flex-col gap-6 p-8 bg-gray-100">
      {profiles.map((profile, index) => (
        <div key={index} className="bg-white p-4 rounded-lg shadow-lg">
          <div className={`relative inline-block ${profile.ringStyle} rounded-full`}>
            <img
              src={profile.image}
              alt={profile.name}
              className="w-24 h-24 rounded-full object-cover"
            />
          </div>
          <h3 className="mt-4 font-bold text-lg">{profile.name}</h3>
          <p className="text-gray-600">{profile.status}</p>
        </div>
      ))}
    </div>
  );
}

Interactive Button Ring Animation

This example shows how to create buttons with custom ring animations for different interaction states.

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

// App.js
export default function InteractiveButtons() {
  return (
    <div className="flex flex-col gap-6 items-center justify-center min-h-screen bg-gray-50 p-8">
      <button className="px-8 py-3 bg-purple-600 text-white rounded-lg
        transition-all duration-300
        hover:ring-hover hover:ring-purple-300
        active:ring-active active:ring-purple-400
        focus:ring-focus focus:ring-purple-500 focus:outline-none">
        Hover Me
      </button>
      
      <button className="px-8 py-3 bg-teal-600 text-white rounded-lg
        transition-all duration-300
        hover:ring-hover hover:ring-teal-300
        active:ring-active active:ring-teal-400
        focus:ring-focus focus:ring-teal-500 focus:outline-none">
        Click Me
      </button>
      
      <button className="px-8 py-3 bg-rose-600 text-white rounded-lg
        transition-all duration-300
        hover:ring-hover hover:ring-rose-300
        active:ring-active active:ring-rose-400
        focus:ring-focus focus:ring-rose-500 focus:outline-none">
        Focus Me
      </button>
    </div>
  );
}

Form Input Ring Width Validation

This example demonstrates custom ring widths for form input validation states.

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

// App.js
export default function ValidationForm() {
  return (
    <div className="max-w-md mx-auto p-6 bg-white rounded-xl shadow-lg mt-10">
      <h2 className="text-2xl font-bold mb-6">Registration Form</h2>
      
      <div className="space-y-4">
        <div className="space-y-2">
          <label className="block text-sm font-medium text-gray-700">
            Email
          </label>
          <input
            type="email"
            className="w-full p-3 border border-gray-300 rounded-lg
              focus:outline-none focus:ring-success focus:ring-green-200
              focus:border-green-500"
            placeholder="Valid email input"
          />
        </div>

        <div className="space-y-2">
          <label className="block text-sm font-medium text-gray-700">
            Password
          </label>
          <input
            type="password"
            className="w-full p-3 border border-red-300 rounded-lg
              focus:outline-none ring-error ring-red-200"
            placeholder="Invalid password input"
          />
          <p className="text-red-500 text-sm">Password must be at least 8 characters</p>
        </div>

        <div className="space-y-2">
          <label className="block text-sm font-medium text-gray-700">
            Username
          </label>
          <input
            type="text"
            className="w-full p-3 border border-yellow-300 rounded-lg
              focus:outline-none ring-warning ring-yellow-200"
            placeholder="Username under review"
          />
          <p className="text-yellow-600 text-sm">Username availability being checked...</p>
        </div>
      </div>
    </div>
  );
}

Best Practices

Maintain Design Consistency

When applying Tailwind CSS Ring Width utilities, you should always prioritize maintaining a consistent visual design across your application. Stick to predefined ring widths or customize specific values within your Tailwind configuration file, ensuring that all key interface elements share a similar style language.

Additionally, ensure that your ring colors align with the overall theme palette of your application. For example, if you're working on a UI with primarily blue and gray tones, select corresponding ring colors from Tailwind's default or extended palette. Using Tailwind's extend feature, you can also define additional customizable ring colors within your design specifications.

This is a live editor. Play around with it!
import tailwindConfig from "./tailwind.config.js";
tailwind.config = tailwindConfig;

export default function ConsistentRings() {
  return (
    <div className="flex gap-4 p-8 bg-gray-100">
      <button className="ring ring-blue-500 text-white px-4 py-2 rounded-lg bg-blue-700">
        Submit
      </button>
      <input
        type="text"
        className="ring ring-blue-500 border-none p-2 rounded-md bg-white"
        placeholder="Enter text here"
      />
    </div>
  );
}

Accessibility Considerations

Enhance Readability and Navigability

Ring Width plays an integral role in making content easier to read and navigate, especially for users reliant on assistive technologies or complex interfaces. By deliberately applying wide focus rings (e.g., ring-4 or ring-8), you can create high-visibility UI components. Focus indicators should be intentionally contrasted against backgrounds to provide instant visual clarity.

This is a live editor. Play around with it!
export default function ReadableFocus() {
  return (
    <div className="flex gap-4 p-8 bg-gray-100">
      <button className="ring-4 ring-green-400 focus:ring-8 focus:ring-green-600 text-white bg-green-700 rounded-lg px-6 py-2">
        Primary Button
      </button>
      <a
        href="/"
        className="ring-2 ring-indigo-400 focus:ring-4 focus:ring-indigo-700 text-indigo-600 underline"
      >
        Accessible Link
      </a>
    </div>
  );
}

By coupling focus states with contrasting Ring Widths, your design becomes inherently more inclusive and navigable for all user types.

Support Accessible Interactive Elements

Leverage Tailwind's focus utilities and Ring Width classes to make all interactive components accessible and perceivable on every device. For instance, inputs, buttons, and focusable custom elements such as div with tabIndex should include well-defined focus indicators. Use .focus:ring-* or .focus-visible:ring-* to create clear interaction cues.

This is a live editor. Play around with it!
export default function InteractiveAccessibility() {
  return (
    <div className="grid gap-4 p-6 bg-gray-50">
      <button className="ring-2 ring-blue-400 focus-visible:ring-4 focus-visible:ring-blue-600 rounded-md px-4 py-2 bg-blue-700 text-white hover:ring-4">
        Accessible Button
      </button>
      <div
        className="ring-2 ring-gray-500 focus:ring-4 focus:ring-indigo-500 p-5 rounded-lg"
        tabIndex={0}
        aria-label="Interactive Div"
      >
        Interactive Focusable Element
      </div>
    </div>
  );
}

By ensuring proper implementation of Ring Width utilities, you create user-friendly interactive components that comply with accessibility standards and accommodate diverse user needs.