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.
Class | Properties | Example |
---|---|---|
ring-0 | box-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-1 | box-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-2 | box-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); | <div className="ring-2"></div> |
ring | box-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color); | <div className="ring"></div> |
ring-4 | box-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-8 | box-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:
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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.