Tailwind CSS Stroke Width
Stroke width in CSS controls the thickness of outlines in SVG elements. This property plays a vital role in creating visual contrast, emphasizing boundaries, or refining iconography within your web designs.
In this guide, we'll explore how to handle stroke width in Tailwind CSS, organize it based on user states and responsive design, and even customize it to better fit your design requirements.
Class | Properties | Example |
---|---|---|
stroke-0 | stroke-width: 0; | <div className="stroke-0"></div> |
stroke-1 | stroke-width: 1; | <div className="stroke-1"></div> |
stroke-2 | stroke-width: 2; | <div className="stroke-2"></div> |
Overview of Stroke Width
Adding the Stroke Width
Tailwind provides the following three pre-defined classes to add stroke width to the SVGs- stroke-0
, stroke-1
, and stroke-2
.
export default function Graphic() { return ( <div className="w-screen h-screen flex justify-center items-center bg-gray-50"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" className="stroke-indigo-600 stroke-2 fill-transparent" width="200" height="200" > <circle cx="50" cy="50" r="40" /> </svg> </div> ); }
States and Responsiveness
When it comes to applying conditional changes, such as hover effects or responsiveness across breakpoints, Tailwind makes it seamless with its built-in utility modifiers.
Hover and Focus States
Tailwind allows you to modify stroke width on hover, focus, and other states by prefixing the desired state before the utility. Here’s an implementation focused on a hover effect:
export default function InteractiveGraphic() { return ( <div className="w-screen h-screen flex justify-center items-center bg-gray-100"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" className="stroke-1 hover:stroke-2 stroke-red-500 fill-transparent transition-all" width="200" height="200" > <circle cx="50" cy="50" r="40" /> </svg> </div> ); }
Breakpoint Modifiers
Responsive design is pivotal in modern web development, and Tailwind ensures complete flexibility with media query modifiers. Pairing these modifiers with stroke width makes scaling SVG designs by screen size effortless.
export default function ResponsiveGraphic() { return ( <div className="w-screen h-screen flex justify-center items-center bg-gray-200"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" className="stroke-blue-600 md:stroke-1 lg:stroke-2 fill-transparent" width="200" height="200" > <circle cx="50" cy="50" r="40" /> </svg> </div> ); }
Custom Stroke Width
When the predefined options in Tailwind CSS don’t meet your specific design requirements, customization comes in handy. Tailwind provides mechanisms to extend your theme with custom stroke values or directly apply arbitrary values.
Extending the Theme
The recommended approach in customizing your stroke widths is by extending the Tailwind configuration file, tailwind.config.js
. In the extend
section, you can define additional rules for stroke width as follows:
With the above configuration, you define two new stroke-width values: 1.5
and 3
. Use them in your design like this:
import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; export default function CustomStroke() { return ( <div className="w-screen h-screen flex justify-center items-center"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" className="stroke-green-700 stroke-1.5 fill-transparent" width="200" height="200" > <circle cx="50" cy="50" r="40" /> </svg> </div> ); }
Using Arbitrary Values
When working with non-standard design systems or exploring unconventional thicknesses, arbitrary values offer immediate utility without altering configurations.
Consider applying a 2.5px
stroke width directly within the JSX:
export default function ArbitraryStroke() { return ( <div className="w-screen h-screen flex justify-center items-center"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" className="stroke-[2.5px] stroke-orange-300 fill-transparent" width="200" height="200" > <circle cx="50" cy="50" r="40" /> </svg> </div> ); } // Tailwind utilities applied: // [stroke-width:2.5px] -> Arbitrarily applied stroke width // .stroke-orange-500 -> Orange stroke color // .fill-transparent -> Circle will remain uncolored
This method unleashes unrestricted possibilities for configurations and prototyping that deviate from conventional rules.
Real World Examples
Chart Legend
A chart with colored indicators using stroke-width
for emphasis.
export const ChartLegend = () => { const data = [ { id: 1, label: "Revenue", color: "blue", value: "$1.2M", trend: "up" }, { id: 2, label: "Expenses", color: "red", value: "$800K", trend: "down" }, { id: 3, label: "Profit", color: "green", value: "$400K", trend: "up" }, { id: 4, label: "Growth", color: "purple", value: "15%", trend: "up" }, { id: 5, label: "Customers", color: "orange", value: "2.5K", trend: "up" }, { id: 6, label: "Churn", color: "pink", value: "2%", trend: "down" } ]; return ( <div className="p-6 bg-white"> <div className="grid grid-cols-2 gap-4"> {data.map((item) => ( <div key={item.id} className="flex items-center p-3 rounded-lg hover:bg-gray-50"> <svg className="w-6 h-6"> <circle cx="12" cy="12" r="4" className={`fill-${item.color}-500 stroke-${item.color}-600 stroke-2`} /> </svg> <div className="ml-3"> <div className="text-sm font-medium text-gray-900">{item.label}</div> <div className="text-lg font-semibold">{item.value}</div> </div> <svg className={`w-4 h-4 ml-auto ${ item.trend === 'up' ? 'stroke-green-500' : 'stroke-red-500' } stroke-[5]`}> <path d={item.trend === 'up' ? 'M8 4l4 4-4 4' : 'M4 8l4-4 4 4'} /> </svg> </div> ))} </div> </div> ); }; export default ChartLegend;
Skill Chart
A skill chart with varying stroke width based on the skill level.
const SkillChart = () => { const data = [ { skill: "JavaScript", level: 90, category: "frontend", icon: "https://images.unsplash.com/photo-1633356122544-f134324a6cee", alt: "JavaScript code" }, { skill: "Python", level: 85, category: "backend", icon: "https://images.unsplash.com/photo-1649180556628-9ba704115795", alt: "Python code" }, { skill: "UI Design", level: 75, category: "design", icon: "https://images.unsplash.com/photo-1586717791821-3f44a563fa4c", alt: "UI design interface" }, { skill: "DevOps", level: 70, category: "operations", icon: "https://images.unsplash.com/photo-1667372393119-3d4c48d07fc9", alt: "DevOps tools" }, { skill: "Database", level: 80, category: "backend", icon: "https://images.unsplash.com/photo-1544383835-bda2bc66a55d", alt: "Database server" }, { skill: "Testing", level: 65, category: "quality", icon: "https://images.unsplash.com/photo-1614741118887-7a4ee193a5fa", alt: "Testing process" } ]; return ( <div className="p-8 bg-white"> <div className="max-w-3xl mx-auto"> <div className="relative w-full h-96"> {data.map((item, index) => { const angle = (index * 360) / data.length; const radius = (item.level / 100) * 150; const x = Math.cos((angle * Math.PI) / 180) * radius + 150; const y = Math.sin((angle * Math.PI) / 180) * radius + 150; return ( <div key={item.skill}> <svg className="absolute top-0 left-0 w-full h-full"> <line x1="150" y1="150" x2={x} y2={y} className={` ${item.category === 'frontend' ? 'stroke-[4]' : item.category === 'backend' ? 'stroke-[3]' : 'stroke-2' } stroke-blue-500 `} /> </svg> <div className="absolute w-12 h-12 rounded-full overflow-hidden" style={{ left: `${x}px`, top: `${y}px`, transform: 'translate(-50%, -50%)' }} > <img src={item.icon} alt={item.alt} className="w-full h-full object-cover" /> </div> </div> ); })} </div> </div> </div> ); }; export default SkillChart;
Interactive Music Visualizer
An audio wave visualization with dynamic stroke widths based on frequency intensity.
const MusicVisualizer = () => { const tracks = [ { id: 1, title: "Summer Breeze", artist: "Ocean Waves", duration: "3:45", intensity: "high", cover: "https://images.unsplash.com/photo-1459749411175-04bf5292ceea" }, { id: 2, title: "Mountain Echo", artist: "Alpine Sound", duration: "4:20", intensity: "medium", cover: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745" }, { id: 3, title: "Urban Night", artist: "City Lights", duration: "3:55", intensity: "low", cover: "https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4" }, { id: 4, title: "Desert Wind", artist: "Sand Storms", duration: "5:10", intensity: "medium", cover: "https://images.unsplash.com/photo-1459749411175-04bf5292ceea" }, { id: 5, title: "Forest Rain", artist: "Nature Sounds", duration: "4:15", intensity: "high", cover: "https://images.unsplash.com/photo-1470225620780-dba8ba36b745" }, { id: 6, title: "Night Sky", artist: "Star Gazers", duration: "6:30", intensity: "low", cover: "https://images.unsplash.com/photo-1511671782779-c97d3d27a1d4" } ]; return ( <div className="p-8 bg-gray-900"> <div className="max-w-3xl mx-auto space-y-6"> {tracks.map((track) => ( <div key={track.id} className="bg-gray-800 rounded-lg p-4 flex items-center gap-4"> <img src={track.cover} alt={track.title} className="w-16 h-16 rounded-md object-cover" /> <div className="flex-1"> <h3 className="text-white font-medium">{track.title}</h3> <p className="text-gray-400">{track.artist}</p> </div> <svg className="w-32 h-8" viewBox="0 0 100 24" > {[...Array(8)].map((_, i) => ( <line key={i} x1={10 + i * 12} y1="12" x2={10 + i * 12} y2="20" className={`stroke-purple-500 ${ track.intensity === 'high' ? 'stroke-[3]' : track.intensity === 'medium' ? 'stroke-2' : 'stroke-1' }`} /> ))} </svg> <span className="text-gray-400 w-16 text-right">{track.duration}</span> </div> ))} </div> </div> ); }; export default MusicVisualizer;
Progress Circle Dashboard
A circular progress indicator for a fitness tracking dashboard showing different workout completion states.
const WorkoutProgress = () => { const workouts = [ { title: "Morning Yoga", completion: 85, category: "Flexibility", duration: "45 mins", intensity: "Medium", calories: 320, instructor: "Sarah Chen" }, { title: "HIIT Training", completion: 60, category: "Cardio", duration: "30 mins", intensity: "High", calories: 450, instructor: "Mike Johnson" }, { title: "Strength Training", completion: 40, category: "Strength", duration: "60 mins", intensity: "High", calories: 580, instructor: "David Park" }, { title: "Pilates", completion: 95, category: "Core", duration: "50 mins", intensity: "Medium", calories: 280, instructor: "Emma Wilson" }, { title: "Swimming", completion: 25, category: "Cardio", duration: "45 mins", intensity: "Medium", calories: 400, instructor: "James Brown" }, { title: "Boxing", completion: 75, category: "Mixed", duration: "40 mins", intensity: "High", calories: 520, instructor: "Alex Martinez" } ]; return ( <div className="grid grid-cols-3 gap-6 p-6 bg-gray-50"> {workouts.map((workout, index) => ( <div key={index} className="bg-white p-4 rounded-lg shadow-md"> <div className="relative w-24 h-24 mx-auto mb-4"> <svg className="w-full h-full transform -rotate-90"> <circle className="stroke-gray-200" strokeWidth="8" fill="none" r="40" cx="48" cy="48" /> <circle className={`stroke-blue-500 ${ workout.completion > 80 ? 'stroke-[6]' : workout.completion > 50 ? 'stroke-[4]' : 'stroke-2' }`} strokeWidth="8" fill="none" r="40" cx="48" cy="48" strokeDasharray={`${2 * Math.PI * 40}`} strokeDashoffset={`${2 * Math.PI * 40 * (1 - workout.completion / 100)}`} /> </svg> <span className="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-xl font-bold"> {workout.completion}% </span> </div> <div className="text-center"> <h3 className="font-bold text-lg mb-1">{workout.title}</h3> <p className="text-gray-600">{workout.category}</p> <div className="mt-2 text-sm text-gray-500"> <p>{workout.duration} • {workout.calories} cal</p> <p className="mt-1">Instructor: {workout.instructor}</p> </div> </div> </div> ))} </div> ); }; export default WorkoutProgress;
Animated Loading Spinner
A loading spinner with animated stroke width transitions.
export default function LoadingSpinner() { const loadingStates = [ { id: 1, status: "Processing payment" }, { id: 2, status: "Validating order" }, { id: 3, status: "Checking inventory" }, { id: 4, status: "Preparing shipment" }, { id: 5, status: "Generating invoice" }, { id: 6, status: "Sending confirmation" } ]; return ( <div className="p-8 flex flex-col items-center"> <svg className="animate-spin h-16 w-16 text-blue-500" viewBox="0 0 24 24" > <circle className="opacity-25 stroke-[1]" cx="12" cy="12" r="10" stroke="currentColor" fill="none" /> <path className="opacity-75 stroke-2" fill="none" stroke="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> <div className="mt-8 space-y-2"> {loadingStates.map((state) => ( <div key={state.id} className="flex items-center justify-center text-gray-600" > <span className="animate-pulse">{state.status}</span> </div> ))} </div> </div> ); }
Customization Examples
Dynamic Progress Circle with Custom Stroke Width
This example demonstrates a circular progress indicator with a customized stroke width for better visibility on larger screens.
// App.js import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; export default function CircularProgress() { return ( <div className="flex items-center justify-center h-screen bg-gray-100"> <svg className="transform -rotate-90 w-64 h-64" viewBox="0 0 100 100" > <circle className="stroke-gray-200 fill-none" cx="50" cy="50" r="45" /> <circle className="stroke-blue-600 fill-none stroke-thick" cx="50" cy="50" r="45" strokeDasharray="283" strokeDashoffset="70" /> <text x="50" y="50" className="text-3xl font-bold text-blue-600" textAnchor="middle" dy=".3em" > 75% </text> </svg> </div> ) }
Animated Path Drawing
A signature-like path drawing effect with varying stroke widths based on velocity.
// App.js import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; import React, { useState, useRef } from 'react'; const AnimatedPathDrawing = () => { const [isDrawing, setIsDrawing] = useState(false); const [paths, setPaths] = useState([]); const [currentPath, setCurrentPath] = useState([]); const canvasRef = useRef(null); const lastPoint = useRef(null); const lastVelocity = useRef(0); const getStrokeWidth = (velocity) => { if (velocity < 2) return 'stroke-thick'; if (velocity < 5) return 'stroke-medium'; return 'stroke-thin'; }; const handleMouseDown = (e) => { setIsDrawing(true); const point = getPoint(e); setCurrentPath([point]); lastPoint.current = point; }; const handleMouseMove = (e) => { if (!isDrawing) return; const newPoint = getPoint(e); const velocity = getVelocity(lastPoint.current, newPoint); lastVelocity.current = velocity; setCurrentPath(prev => [...prev, newPoint]); lastPoint.current = newPoint; }; const handleMouseUp = () => { if (currentPath.length > 1) { setPaths(prev => [...prev, { points: currentPath, width: getStrokeWidth(lastVelocity.current) }]); } setCurrentPath([]); setIsDrawing(false); }; const getPoint = (e) => { const rect = canvasRef.current.getBoundingClientRect(); return { x: e.clientX - rect.left, y: e.clientY - rect.top }; }; const getVelocity = (p1, p2) => { if (!p1) return 0; return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); }; const generatePath = (points) => { if (points.length < 2) return ''; const d = points.reduce((acc, point, i) => { if (i === 0) return `M ${point.x} ${point.y}`; return `${acc} L ${point.x} ${point.y}`; }, ''); return d; }; return ( <div className="p-8 bg-white rounded-lg shadow-lg"> <svg ref={canvasRef} className="w-full h-96 bg-gray-50 rounded-md cursor-crosshair" onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} onMouseLeave={handleMouseUp} > {paths.map((path, i) => ( <path key={i} d={generatePath(path.points)} className={`fill-none stroke-blue-500 ${path.width}`} strokeLinecap="round" strokeLinejoin="round" /> ))} {currentPath.length > 1 && ( <path d={generatePath(currentPath)} className="fill-none stroke-blue-500 stroke-signature" strokeLinecap="round" strokeLinejoin="round" /> )} </svg> <button onClick={() => setPaths([])} className="mt-4 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" > Clear Canvas </button> </div> ); }; export default AnimatedPathDrawing;
Network Graph Connector
A component that visualizes network connections with varying stroke widths based on connection strength.
// App.js import tailwindConfig from "./tailwind.config.js"; tailwind.config = tailwindConfig; import React, { useState } from 'react'; const NetworkGraph = () => { const nodes = [ { id: 1, x: 100, y: 100, label: 'A' }, { id: 2, x: 300, y: 150, label: 'B' }, { id: 3, x: 200, y: 250, label: 'C' }, { id: 4, x: 400, y: 300, label: 'D' } ]; const connections = [ { from: 1, to: 2, strength: 'strong' }, { from: 2, to: 3, strength: 'medium' }, { from: 3, to: 4, strength: 'weak' }, { from: 1, to: 4, strength: 'medium' } ]; const [activeNode, setActiveNode] = useState(null); const getConnectionClass = (strength) => { switch (strength) { case 'weak': return 'stroke-connection-weak stroke-gray-300'; case 'medium': return 'stroke-connection-medium stroke-gray-500'; case 'strong': return 'stroke-connection-strong stroke-gray-700'; default: return 'stroke-connection-weak stroke-gray-300'; } }; return ( <div className="w-full max-w-4xl mx-auto p-4"> <svg className="w-full h-96 bg-white rounded-lg"> {connections.map((conn, idx) => { const fromNode = nodes.find(n => n.id === conn.from); const toNode = nodes.find(n => n.id === conn.to); const isActive = activeNode === conn.from || activeNode === conn.to; return ( <line key={idx} x1={fromNode.x} y1={fromNode.y} x2={toNode.x} y2={toNode.y} className={`${getConnectionClass(conn.strength)} ${ isActive ? 'opacity-100' : 'opacity-50' } transition-opacity duration-300`} /> ); })} {nodes.map((node) => ( <g key={node.id} onMouseEnter={() => setActiveNode(node.id)} onMouseLeave={() => setActiveNode(null)} className="cursor-pointer" > <circle cx={node.x} cy={node.y} r="20" className="fill-white stroke-gray-800 stroke-node hover:fill-gray-100 transition-colors duration-300" /> <text x={node.x} y={node.y} textAnchor="middle" dominantBaseline="middle" className="text-sm font-medium pointer-events-none" > {node.label} </text> </g> ))} </svg> </div> ); }; export default NetworkGraph;
Best Practices
Maintain Design Consistency
Ensuring a consistent design across your project is essential when using stroke width in Tailwind CSS. By applying a predefined set of stroke widths and matching them with appropriately themed colors, you create a design language that users can intuitively follow.
For instance, defining thin strokes for secondary elements and bold ones for primary elements establishes visual hierarchies and improves navigability. Extend your tailwind.config.js
file to create stroke width values that align with your branding and design guidelines, making it simpler to apply consistent styling across different components.
Optimize for Reusability
Leverage Tailwind’s configuration capabilities to create reusable stroke width options and integrate these into component libraries. For instance, defining a custom stroke-primary
and stroke-secondary
in your Tailwind theme ensures your team always uses the appropriate stroke values for primary and supporting content, respectively.
Document a system of scalable stroke widths paired with versions tailored to states like hover and disabled. Doing this minimizes redundancies in style declarations while allowing components to adapt to design evolutions naturally. Combine good documentation with Tailwind’s @apply
directive for embedding preconfigured stroke widths into modular CSS definitions.
Accessibility Considerations
Enhance Readability and Navigability
Stroke-width
plays a key role in improving readability and navigability. Thicker strokes can emphasize important elements, making them easier to identify. For example, in SVG-based navigation icons, a slightly increased stroke width (stroke-2
) can help visually impaired users distinguish between items.
Use stroke-width
to create clear boundaries and separation between components. When designing charts or diagrams, ensure that the stroke widths are proportional to the data being represented, making the content more accessible to all users. This approach enhances overall usability while catering to diverse audience needs.
Support Accessible Interactive Elements
Interactive elements like buttons, links, or toggles benefit from thoughtful stroke-width
applications. Thicker strokes on hover
or focus
states can provide clear visual feedback, enhancing the accessibility of these components. Use Tailwind’s state variants (hover:
, focus:
) to implement these changes efficiently.
When designing form fields or buttons, use consistent stroke-widths to indicate active or error states. For instance, applying stroke-red-500
with stroke-2
can highlight errors clearly, ensuring users understand what needs attention. This approach improves user confidence and reduces frustration.
Ensure that interactive elements remain accessible for keyboard users. Combine stroke-width
adjustments with Tailwind’s outline-*
and focus-visible
utilities to enhance focus styles. This ensures that all users, including those relying on keyboards, can interact with your application seamlessly.