Home / Articles / Color & Theming /
Conic gradients: pie charts and color wheels
Use conic-gradient() to build pie charts, donut charts, progress indicators, and color wheels — all in pure CSS.
How conic-gradient() works
Unlike linear and radial gradients, a conic gradient sweeps color stops around a center point like a clock hand. The gradient starts at the top (12 o'clock by default) and rotates clockwise through 360 degrees. This angular distribution makes it perfect for circular data visualization.
/* Basic syntax */
.element {
background: conic-gradient(
from 0deg at 50% 50%, /* start angle, center position */
oklch(0.6 0.2 265), /* color at 0° */
oklch(0.6 0.2 145), /* color at 180° (midpoint) */
oklch(0.6 0.2 265) /* color at 360° (wraps back) */
);
}
/* Shorthand — 'from' and 'at' are optional */
.simple {
background: conic-gradient(
oklch(0.6 0.2 25),
oklch(0.6 0.2 145),
oklch(0.6 0.2 265),
oklch(0.6 0.2 25) /* repeat first color to close the loop */
);
}
When the first and last color stops match, the gradient forms a seamless loop. When they differ, there is a hard seam at the 12 o'clock position.
Building a pie chart
Pie charts use hard color stops — two colors meet at the same angle, creating a sharp edge instead of a smooth blend. Each segment occupies a percentage of the 360-degree circle.
/* Three-segment pie chart */
.pie-chart {
width: 12rem;
height: 12rem;
border-radius: 50%;
background: conic-gradient(
oklch(0.60 0.20 265) 0% 45%, /* Blue: 45% */
oklch(0.65 0.18 145) 45% 78%, /* Green: 33% */
oklch(0.70 0.16 55) 78% 100% /* Orange: 22% */
);
}
/* The double-stop syntax (e.g. 0% 45%) means:
"start at 0%, end at 45%" — creating a solid segment
with no transition between colors. */
/* Four segments with a gap effect */
.pie-gapped {
width: 12rem;
height: 12rem;
border-radius: 50%;
background: conic-gradient(
oklch(0.60 0.20 265) 0% 44%,
oklch(0.13 0.02 260) 44% 45%, /* 1% dark gap */
oklch(0.65 0.18 145) 45% 77%,
oklch(0.13 0.02 260) 77% 78%, /* 1% dark gap */
oklch(0.70 0.16 55) 78% 99%,
oklch(0.13 0.02 260) 99% 100% /* 1% dark gap */
);
}
The gap technique inserts a thin segment matching the background color between each data segment. This adds visual separation and a more polished look.
Creating a donut chart
A donut chart is a pie chart with a hole in the center. You can achieve this with a circular mask or by layering a pseudo-element on top.
/* Donut chart using mask */
.donut {
width: 12rem;
height: 12rem;
border-radius: 50%;
background: conic-gradient(
oklch(0.60 0.20 265) 0% 60%,
oklch(0.65 0.18 145) 60% 85%,
oklch(0.55 0.20 300) 85% 100%
);
/* Radial mask: transparent center, opaque ring */
mask: radial-gradient(
circle,
transparent 55%,
oklch(0 0 0) 56%
);
}
/* Alternative: pseudo-element approach */
.donut-alt {
width: 12rem;
height: 12rem;
border-radius: 50%;
background: conic-gradient(
oklch(0.60 0.20 265) 0% 60%,
oklch(0.65 0.18 145) 60% 85%,
oklch(0.55 0.20 300) 85% 100%
);
position: relative;
}
.donut-alt::after {
content: "";
position: absolute;
inset: 20%;
border-radius: 50%;
background: oklch(0.13 0.02 260); /* matches page background */
}
The mask approach is more flexible because it creates a true transparent hole — the content behind shows through. The pseudo-element approach is simpler but requires matching the background color.
Progress rings
A progress indicator shows a partial fill around a circle. Using a custom property for the percentage makes it easy to update from HTML or JavaScript.
/* CSS-only progress ring */
.progress-ring {
--progress: 72; /* percentage, 0–100 */
width: 8rem;
height: 8rem;
border-radius: 50%;
background: conic-gradient(
oklch(0.65 0.20 265) 0%,
oklch(0.65 0.20 265) calc(var(--progress) * 1%),
oklch(0.22 0.03 260) calc(var(--progress) * 1%),
oklch(0.22 0.03 260) 100%
);
mask: radial-gradient(circle, transparent 60%, oklch(0 0 0) 61%);
/* Center the percentage text */
display: grid;
place-items: center;
}
.progress-ring::after {
content: attr(data-value) "%";
font-size: 1.25rem;
font-weight: 700;
color: oklch(0.93 0.01 260);
}
/* Usage: <div class="progress-ring" data-value="72"
style="--progress: 72"></div> */
The gradient has two segments: the filled portion (brand color) and the unfilled track (dark gray). The mask punches out the center to create the ring shape.
Building a color wheel
A smooth conic gradient that cycles through all hues creates a color wheel. With OKLCH, you can ensure every hue appears at the same perceived brightness — something impossible with HSL.
/* OKLCH color wheel — perceptually uniform */
.color-wheel {
width: 14rem;
height: 14rem;
border-radius: 50%;
background: conic-gradient(
in oklch longer hue,
oklch(0.7 0.18 0),
oklch(0.7 0.18 60),
oklch(0.7 0.18 120),
oklch(0.7 0.18 180),
oklch(0.7 0.18 240),
oklch(0.7 0.18 300),
oklch(0.7 0.18 360)
);
}
/* Donut color wheel */
.color-wheel-ring {
width: 14rem;
height: 14rem;
border-radius: 50%;
background: conic-gradient(
in oklch longer hue,
oklch(0.7 0.18 0),
oklch(0.7 0.18 120),
oklch(0.7 0.18 240),
oklch(0.7 0.18 360)
);
mask: radial-gradient(circle, transparent 65%, oklch(0 0 0) 66%);
}
The in oklch longer hue interpolation hint tells the browser to interpolate through the OKLCH color space, taking the longer path around the hue wheel. This ensures smooth color transitions between stops.
Repeating conic gradients
The repeating-conic-gradient() function tiles a pattern around the center point. This is useful for segmented indicators, loading spinners, and decorative patterns.
/* Segmented progress indicator — 12 ticks */
.segmented-ring {
width: 10rem;
height: 10rem;
border-radius: 50%;
background: repeating-conic-gradient(
oklch(0.60 0.20 265) 0deg 25deg,
oklch(0.22 0.03 260) 25deg 30deg
);
mask: radial-gradient(circle, transparent 60%, oklch(0 0 0) 61%);
/* 25° filled + 5° gap = 30° per segment, 12 segments total */
}
/* Starburst / pinwheel pattern */
.starburst {
width: 12rem;
height: 12rem;
border-radius: 50%;
background: repeating-conic-gradient(
oklch(0.50 0.20 265) 0deg 15deg,
oklch(0.35 0.15 265) 15deg 30deg
);
}
/* Checkerboard using conic-gradient */
.checkerboard {
background: conic-gradient(
oklch(0.25 0.02 260) 0.25turn,
oklch(0.18 0.02 260) 0.25turn 0.5turn,
oklch(0.25 0.02 260) 0.5turn 0.75turn,
oklch(0.18 0.02 260) 0.75turn
);
background-size: 2rem 2rem;
}
The repeating variant is particularly efficient for patterns with many identical segments — you define one segment and the browser repeats it to fill the full 360 degrees.