CSS Starburst

A slowly rotating starburst drawn from one repeating-conic-gradient, with a clip-path badge and a background burst variant.

Published September 8, 2026 Intermediate 9 min read

A CSS starburst is a fan of rays radiating from a center point, and it needs exactly one element to draw. repeating-conic-gradient sweeps a color around a circle and repeats a pattern as it goes, so a wedge followed by a gap, repeated, is a set of rays. Changing the ray count means changing one angle rather than adding markup.

Turning it is the other half. A gradient's own angle cannot be animated directly without registering it as a custom property first, so the element holding the gradient is what rotates. At forty-eight seconds a revolution the movement is slow enough to read as a shimmer rather than a spinner, which is usually what a starburst behind a headline or a badge is for.

One gradient, turning slowly

New this week

two ray layers, turning at different speeds

HTML

<div class="sb-burst">
  <div class="sb-rays" aria-hidden="true"></div>
  <div class="sb-rays sb-rays-fine" aria-hidden="true"></div>

  <div class="sb-core">
    <b>New</b>
    <span>this week</span>
  </div>
</div>

CSS

.sb-burst {
  position: relative;
  width: 230px;
  height: 230px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* One repetition is 15deg and paints color for the first 5 of them,
   so there are 24 rays and each gap is twice its ray. The box turns,
   not the gradient: a gradient angle will not interpolate unless it
   is registered through @property first. */
.sb-rays {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg,
    #b8ff57 0deg 5deg,
    transparent 5deg 15deg);
  -webkit-mask-image: radial-gradient(circle at 50% 50%, transparent 27%, #000 36%, #000 60%, transparent 80%);
  mask-image: radial-gradient(circle at 50% 50%, transparent 27%, #000 36%, #000 60%, transparent 80%);
  animation: sb-turn 48s linear infinite;
}

/* a finer set going the other way, so the rays never read as one
   solid wheel */
.sb-rays-fine {
  background: repeating-conic-gradient(from 0deg,
    #38bdf8 0deg 1.6deg,
    transparent 1.6deg 9deg);
  opacity: .5;
  animation: sb-turn-back 72s linear infinite;
}

@keyframes sb-turn {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

@keyframes sb-turn-back {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }
}

/* a sibling of the rotating layers, so it never inherits the turn */
.sb-core {
  position: relative;
  z-index: 1;
  width: 108px;
  height: 108px;
  border-radius: 50%;
  background: #141415;
  border: 1px solid #2a2a2d;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .1rem;
}

.sb-core b {
  font-size: 1.5rem;
  font-weight: 800;
  letter-spacing: -.03em;
  color: #b8ff57;
  line-height: 1;
}

.sb-core span {
  font-family: ui-monospace, monospace;
  font-size: .55rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: #88888f;
}

@media (prefers-reduced-motion: reduce) {
  .sb-rays { animation: none; }
}

Other ways to build it

A pointed badge cut with clip-path

Same silhouette, different technique. Instead of painting rays with a gradient, this cuts them out of a solid square with a clip-path polygon of forty vertices, alternating between an outer radius of 50% and an inner one of 42%. The distance between those two radii is the whole design: widen it and the points sharpen into a star, narrow it and the shape flattens toward a bottle cap. Because the badge itself rotates, the text inside would go around with it, so the text carries the same animation in the opposite direction at the same duration and stays level.

40% off

HTML

<div class="sb-badge">
  <div class="sb-badge-text">
    <b>40%</b>
    <span>off</span>
  </div>
</div>

CSS

/* uses @keyframes sb-turn, @keyframes sb-turn-back from the main example above; the rules here are
   what changes, not the whole file */
/* forty vertices, alternating 50% and 42% from the center. The gap
   between those two radii is what makes the points sharp or shallow. */
.sb-badge {
  position: relative;
  width: 190px;
  height: 190px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #b8ff57;
  color: #0c0c0d;
  clip-path: polygon(
    50.0% 0.0%, 56.6% 8.5%, 65.5% 2.4%, 69.1% 12.6%,
    79.4% 9.5%, 79.7% 20.3%, 90.5% 20.6%, 87.4% 30.9%,
    97.6% 34.5%, 91.5% 43.4%, 100.0% 50.0%, 91.5% 56.6%,
    97.6% 65.5%, 87.4% 69.1%, 90.5% 79.4%, 79.7% 79.7%,
    79.4% 90.5%, 69.1% 87.4%, 65.5% 97.6%, 56.6% 91.5%,
    50.0% 100.0%, 43.4% 91.5%, 34.5% 97.6%, 30.9% 87.4%,
    20.6% 90.5%, 20.3% 79.7%, 9.5% 79.4%, 12.6% 69.1%,
    2.4% 65.5%, 8.5% 56.6%, 0.0% 50.0%, 8.5% 43.4%,
    2.4% 34.5%, 12.6% 30.9%, 9.5% 20.6%, 20.3% 20.3%,
    20.6% 9.5%, 30.9% 12.6%, 34.5% 2.4%, 43.4% 8.5%
  );
  animation: sb-turn 90s linear infinite;
}

/* the badge turns, the words do not */
.sb-badge-text {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .1rem;
  animation: sb-turn-back 90s linear infinite;
}

.sb-badge-text b {
  font-size: 1.7rem;
  font-weight: 800;
  letter-spacing: -.03em;
  line-height: 1;
}

.sb-badge-text span {
  font-family: ui-monospace, monospace;
  font-size: .55rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  opacity: .75;
}

A burst behind a heading

The same gradient used as background rather than as an object. The ray layer is far larger than the frame and anchored below it, so only the top of the fan shows and the rays appear to come from somewhere off screen. overflow: hidden on the frame does the cropping. The tint is deliberately faint, because the text on top has to clear its contrast ratio against a ray rather than against the panel behind it, and a ray is the darkest thing actually there.

Ships this Friday every order placed before noon

HTML

<div class="sb-banner">
  <div class="sb-banner-rays" aria-hidden="true"></div>
  <div class="sb-banner-text">
    <b>Ships this Friday</b>
    <span>every order placed before noon</span>
  </div>
</div>

CSS

/* uses @keyframes sb-turn from the main example above; the rules here are
   what changes, not the whole file */
.sb-banner {
  position: relative;
  width: 100%;
  max-width: 460px;
  height: 190px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 12px;
  background: #141415;
  border: 1px solid #2a2a2d;
}

/* bigger than the frame and anchored below it, so only the top of
   the fan is visible and the rays read as coming from off screen */
.sb-banner-rays {
  position: absolute;
  bottom: -190px;
  left: 50%;
  width: 620px;
  height: 620px;
  margin-left: -310px;
  border-radius: 50%;
  background: repeating-conic-gradient(from 0deg,
    rgba(184, 255, 87, .10) 0deg 4deg,
    transparent 4deg 12deg);
  animation: sb-turn 120s linear infinite;
}

.sb-banner-text {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 0 1.5rem;
}

How it works

The rays come from repeating-conic-gradient(from 0deg, accent 0deg 5deg, transparent 5deg 15deg). Each repetition is fifteen degrees wide and paints color for the first five of them, which gives twenty-four rays around the circle with a two to one gap between them. A mask-image built from a radial-gradient fades the rays out at both ends, so they do not start hard at the center or stop hard at the rim. The element then rotates with a plain transform animation, and because a mask that is radially symmetric looks identical at every angle, only the rays appear to move.

The ray count is arithmetic rather than markup. One repetition spans fifteen degrees, and 360 divided by 15 is 24, so there are twenty-four rays. Want forty-five of them? Make the repetition eight degrees. The ratio inside the repetition sets how heavy each ray looks: five degrees of color in a fifteen degree repetition is a third, so the rays are thinner than the gaps between them. Both numbers can be changed independently, which is the advantage this has over drawing rays as elements, where the count is fixed by how many divs you wrote.

Rotating the box rather than the gradient is a deliberate choice, not a workaround. A conic gradient's from angle is not an animatable value on its own: to interpolate it you would have to register an angle through @property and animate that instead, which works in Chromium but is not something to rely on everywhere yet. Rotating the element produces the same picture, runs on the compositor, and needs nothing registered. The one thing to watch is that the rotation turns everything in the box, which is why the badge variant counter-rotates its own text to keep the words level.

The mask is what separates a starburst from a pinwheel. Without it, the rays converge into a solid disc at the middle and stop with a hard edge at the rim, which reads as a color wheel. A radial-gradient mask that runs transparent, then opaque, then transparent again gives the rays a hollow middle for content to sit in and a soft outer fade, so they look like light rather than like a shape. The -webkit-mask-image line is declared first for older WebKit builds, and both lines carry the same value.

Filling the center is a stacking question. The core sits in normal flow inside a flex container while the rays are absolutely positioned behind it, and z-index: 1 on the core keeps it above them. Because the core is a sibling of the rotating layer rather than a child, it does not inherit the rotation, and the text inside it stays still without any counter animation.

The badge variant swaps the gradient for a clip-path polygon, which is a different technique for a similar look. Forty vertices alternate between an outer radius of 50% and an inner radius of 42%, measured from the center of the box. That gap between the two radii is the entire design: widen it and the points sharpen into a star, narrow it and the shape flattens toward a bottle cap. The points are computed once and pasted in, because CSS has no way to generate them, and that is the honest cost of the approach.

CSS properties used

repeating-conic-gradient
Draws every ray from one declaration. The size of a repetition sets the ray count, and the color stop inside it sets how thick each ray is.
mask-image
A radial gradient that fades the rays out at the center and the rim, which is what makes them read as light rather than as a color wheel.
animation
Turns the element holding the gradient. A gradient angle cannot be interpolated without registering it first, so the box is what moves.
transform
rotate() only. It runs on the compositor, so a permanent slow turn costs almost nothing per frame.
clip-path
Cuts the pointed badge out of a plain square, using forty vertices that alternate between two radii.
border-radius
50% on the ray layer, so the gradient is clipped to a circle rather than filling the corners of its box.
z-index
Holds the center content above the rotating layer behind it. The core is a sibling rather than a child, so it never inherits the rotation.

Browser support

FeatureChromeFirefoxSafariEdge
repeating-conic-gradient698312.179
mask-image1205315.4120
animation455.112
transform43.53.112
custom properties49311016
prefers-reduced-motion746310.179

The mask figures need reading carefully. They are the versions for the unprefixed mask-image, and Chromium only dropped the prefix at 120, but -webkit-mask-image has worked there for many years. Both are declared, so the effective support is much older than the table suggests. clip-path with a polygon() shape is not in the table because the Can I Use entry covers the full property including url() references, and its figures would understate a basic shape considerably. Polygon clipping is safe in every current browser. A browser with no mask support shows the rays converging into a solid disc, which is untidy rather than broken, and the center content still sits on top of it.

Accessibility notes

A starburst is decoration, so it should be invisible to assistive technology. The rays carry no information that the text beside them does not already carry, and a screen reader announcing an empty div adds nothing. Put the meaning in the text and leave the ray layers unlabelled, or mark them aria-hidden="true" if they sit inside a labelled region.

Rotation that never stops is the part to be careful with. This turns slowly enough that most people will read it as static, but slow is not the same as absent, and vestibular sensitivity does not scale with speed in a predictable way. The stylesheet answers prefers-reduced-motion by parking every layer at its drawn angle with animation: none, which keeps the shape and removes the movement entirely. Slowing the animation down instead would be the wrong fix, because the request is for no motion rather than less of it.

Contrast belongs to the text, not to the rays. A headline sitting on top of a burst has to clear its contrast ratio against the darkest thing behind it, which is a ray rather than the background. The banner variant uses a very low opacity tint for exactly this reason, so the rays are visible without dragging the text below the threshold.

What you can build with it

  • A price or discount badge. The pointed variant with a number in the middle, which is where this shape is most recognizable.
  • A background behind a headline. Rays fanning up from below a hero heading, cropped by the frame so they appear to come from off screen.
  • An award or achievement mark. A slow turn behind a badge at the moment it is earned, paired with something like the confetti burst for the arrival.
  • A loading or processing state. Faster rotation and a tighter mask turns the same element into a radial spinner without new markup.
  • A section divider. A wide, faint burst behind a heading, which gives a long page a landmark without an image request.

Mistakes worth avoiding

  • Animating the gradient's from angle directly. It will not interpolate unless the angle is registered through @property first, so the gradient jumps to its final angle or does not move at all. Rotate the element instead.
  • Leaving the mask off. The rays converge into a filled disc at the center and stop with a hard edge at the rim, which looks like a color wheel rather than a burst.
  • Rotating a box that contains the text. Everything inside turns with it, so the words go around too. Either keep the content in a sibling element, or counter-rotate it with a matching animation in the opposite direction at the same duration.
  • Forgetting border-radius: 50% on the ray layer. A conic gradient fills its whole box, so the rays reach into the corners and the burst reads as a square.
  • Setting the repetition and the color stop to the same angle. That paints solid color with no gaps, and the rays disappear into a flat disc.

Frequently asked questions

How do you make a starburst in CSS without images?
One element with repeating-conic-gradient. A repetition that paints color for part of its angle and nothing for the rest produces rays, and 360 divided by the repetition size is the ray count. Add a radial mask-image so the rays fade out at the center and the rim instead of forming a solid disc.
Can a conic gradient be animated?
Not its angle, unless you register that angle through @property and animate the custom property instead. The portable answer is to rotate the element the gradient is painted on, which looks the same and runs on the compositor.
How do I change the number of rays?
Change the size of one repetition. Fifteen degrees gives twenty-four rays, eight degrees gives forty-five. The color stop inside the repetition is separate and controls how thick each ray is relative to the gap.
How do you keep the text still while the burst rotates?
Put the text in a sibling element rather than inside the rotating one, which is what the first demo does. If it has to be a child, give it the same animation in the opposite direction and the same duration, which is what the badge variant does.
Is a rotating background bad for accessibility?
It can be, and slowing it down is not a fix. Answer prefers-reduced-motion: reduce with animation: none so the shape stays and the movement stops. Also check the text contrast against a ray rather than against the page background, because the ray is the darkest thing actually behind the words.