Menu

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.

ClassPropertiesExample
stroke-0stroke-width: 0;<div className="stroke-0"></div>
stroke-1stroke-width: 1;<div className="stroke-1"></div>
stroke-2stroke-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.

This is a live editor. Play around with it!
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:

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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:

This is a live editor. Play around with it!
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:

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
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.

This is a live editor. Play around with it!
// 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.

This is a live editor. Play around with it!
// 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.

This is a live editor. Play around with it!
// 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.