Project: How to Make a CSS-Only “Biohazard” Symbol

You can draw the classic biohazard icon with nothing but CSS. No SVGs, no images, no icon fonts. By the end of this article you will build a scalable, themeable biohazard symbol that renders crisply at any size. The approach uses a handful of elements, CSS custom properties, and a few well-placed masks and transforms.

Why a CSS-Only Biohazard Symbol Matters

Pure CSS icons scale without pixelation, inherit color from text or theme variables, and avoid extra network requests. They can be animated with the same rules you already use for hover states and keyframes. When you keep icons in CSS, you keep control in the cascade and remove a dependency on asset pipelines. This biohazard symbol is also a study in shape composition. You will build ring segments, wedges, and a center mark using techniques that transfer to any custom icon work you do next.

Prerequisites

You do not need a graphics editor. You need to be comfortable with positioning, transforms, and pseudo-elements. If you can center a div and understand how border-radius creates circles, you are ready.

  • Basic HTML
  • CSS custom properties
  • CSS pseudo-elements (::before / ::after)

Step 1: The HTML Structure

The markup is minimal on purpose. One wrapper contains three “arms” for the outer arcs, plus a center ring and a core dot. Each arm will be rotated into place. The center ring and dot complete the icon.

<!-- HTML -->
<div class="biohazard" aria-hidden="true">
  <span class="arm"></span>
  <span class="arm"></span>
  <span class="arm"></span>
  <span class="ring"></span>
  <span class="dot"></span>
</div>

Step 2: The Basic CSS & Styling

Set up a consistent drawing environment with CSS variables. The icon reads currentColor, so you can change its color by setting the text color on the wrapper. The background variable is used to “carve” notches using overlayed triangles that match the page behind the icon.

/* CSS */
:root {
  --size: 240px;          /* Icon size */
  --stroke: calc(var(--size) * 0.08);  /* Line thickness for rings */
  --lobe: calc(var(--size) * 1.15);    /* Diameter of outer arcs */
  --offset: calc(var(--size) * 0.28);  /* Distance of lobe centers from middle */
  --bg: #fff8da;          /* Page background behind the icon */
  --icon-color: #0f0f0f;  /* Biohazard color */
}

*,
*::before,
*::after { box-sizing: border-box; }

body {
  min-height: 100svh;
  display: grid;
  place-items: center;
  margin: 0;
  background: var(--bg);
  color: var(--icon-color);
  font-family: ui-sans-serif, system-ui, Segoe UI, Roboto, Helvetica, Arial, Apple Color Emoji, Segoe UI Emoji;
}

.biohazard {
  width: var(--size);
  aspect-ratio: 1;
  position: relative;
  display: grid;
  place-items: center;
  color: currentColor;    /* allows theme control via parent color */
}

.biohazard * {
  position: absolute;
}

Advanced Tip: Drive geometry with custom properties. You will tweak only a few variables to rescale the entire icon. Because the symbol uses currentColor, you can drop it into any theme, or switch modes by changing color on a parent selector.

Step 3: Building the Three Biohazard Arms

Each arm is a circular ring clipped to a sector. Then a small triangular notch softens the inner joint where each arc points toward the center. The arms are rotated in 120 degree increments and pushed away from the center.

/* CSS */
.biohazard .arm {
  left: 50%;
  top: 50%;
  width: var(--lobe);
  height: var(--lobe);
  border: var(--stroke) solid currentColor;
  border-radius: 50%; /* makes the ring; see "make a circle with CSS" */
  transform-origin: 50% 50%;
  /* Keep only a wedge of the ring: a 80deg sector centered on the top axis */
  -webkit-mask: conic-gradient(from -90deg, transparent 0 50deg, #000 50deg 130deg, transparent 130deg 360deg);
          mask: conic-gradient(from -90deg, transparent 0 50deg, #000 50deg 130deg, transparent 130deg 360deg);
}

/* Place and rotate each arm */
.biohazard .arm:nth-of-type(1) {
  transform: translate(-50%, -50%) rotate(0deg) translateY(calc(-1 * var(--offset)));
}
.biohazard .arm:nth-of-type(2) {
  transform: translate(-50%, -50%) rotate(120deg) translateY(calc(-1 * var(--offset)));
}
.biohazard .arm:nth-of-type(3) {
  transform: translate(-50%, -50%) rotate(240deg) translateY(calc(-1 * var(--offset)));
}

/* Inner notch that "bites" into each arc */
.biohazard .arm::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  /* triangle pointing toward the arm centerline; parent rotation handles direction */
  transform: translate(-50%, -6%);
  border-left: calc(var(--stroke) * 0.7) solid transparent;
  border-right: calc(var(--stroke) * 0.7) solid transparent;
  border-top: calc(var(--stroke) * 1.9) solid var(--bg); /* carve-out color = page bg */
  pointer-events: none;
}

How This Works (Code Breakdown)

The arm starts as a circle created by border-radius: 50% on a square box. If you want a refresher on the pattern, see how to make a circle with CSS. Applying a thick border turns that circle into a ring with the exact line weight you want across all three arms.

The conic-gradient mask trims the ring down to a single arc. Masks keep the parts of an element where the mask is opaque and hide the transparent parts. The gradient begins from -90 degrees so the visible sector is centered along the upward axis. You control the arc width with the two numeric stops: 50deg and 130deg in this case leave an 80 degree arc. That narrow sector is what gives the biohazard its distinct arms.

Placement is a two-step transform: rotate N degrees, then translate outward along the local Y axis by the offset. Because the transform origin is the element center (it is anchored at the icon center by translate(-50%, -50%)), rotate changes the direction of the following translateY. That is how all three arms maintain symmetry at 120 degree intervals while sharing one CSS rule.

Each arm’s ::after triangle creates a small inward notch. The triangle uses the border trick: a zero-size box with transparent left and right borders and a colored top border creates a point. If you want to dig into triangle building blocks, here is a quick reference for a triangle-up with CSS. The triangle’s color matches the page background variable, which makes it appear like a cutout in the ring. Because the triangle sits inside the rotated arm, it always points along that arm’s axis without extra math.

Step 4: Building the Center Ring and Core

The center ring and small dot complete the shape. The ring gives the icon its internal boundary, and the dot finishes the classic mark. Both are simple circles; one has a border to form a ring and the other is filled.

/* CSS */
.biohazard .ring {
  left: 50%;
  top: 50%;
  width: calc(var(--size) * 0.42);
  height: calc(var(--size) * 0.42);
  transform: translate(-50%, -50%);
  border: calc(var(--stroke) * 0.6) solid currentColor;
  border-radius: 50%;
}

.biohazard .dot {
  left: 50%;
  top: 50%;
  width: calc(var(--size) * 0.12);
  height: calc(var(--size) * 0.12);
  transform: translate(-50%, -50%);
  background: currentColor;
  border-radius: 50%;
}

How This Works (Code Breakdown)

The ring and the dot are straightforward. Both are centered with translate(-50%, -50%). The ring uses a thinner border so it feels visually lighter than the outer arcs. Feel free to tune these ratios. Because everything references –size and –stroke, the layout scales as a unit.

If your biohazard sits on a non-solid page background, set –bg to match the exact color behind the icon. That keeps the notches invisible while preserving sharp edges. If you need to float the icon on top of images, you can skip the notch triangles for a solid, bold style.

Advanced Techniques: Adding Animations & Hover Effects

A slow spin and a subtle pulse communicate “hazard” without overwhelming the layout. Here is a small animation package that respects system motion preferences. Hover scales the icon slightly and tints the color with a CSS variable for quick theme changes.

/* CSS */
.biohazard {
  transition: transform 200ms ease, color 200ms ease;
}

.biohazard:hover {
  transform: scale(1.04);
  color: #222; /* tweak on hover; keep currentColor linkage */
}

/* Spin the entire mark and gently pulse the center dot */
@keyframes slow-spin {
  0%   { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

@keyframes pulse {
  0%, 100% { transform: translate(-50%, -50%) scale(1); }
  50%      { transform: translate(-50%, -50%) scale(1.12); }
}

/* Apply animations only when allowed */
@media (prefers-reduced-motion: no-preference) {
  .biohazard {
    /* wrap the icon in a relative container and spin a child to avoid scaling text */
  }
  .biohazard .ring { animation: slow-spin 16s linear infinite; }
  .biohazard .dot  { animation: pulse 2.6s ease-in-out infinite; }
}

Note: If you target Safari and Chromium, the conic-gradient mask works without extra flags. Older WebKit builds prefer -webkit-mask; the code above sets both mask and -webkit-mask for safety. Test at your target sizes to confirm crisp arcs.

Accessibility & Performance

Decorative icons should not distract screen reader users, and functional icons need a clear label. The CSS approach in this article keeps the DOM light and GPU-friendly while respecting motion preferences.

Accessibility

If the biohazard symbol is purely decorative, leave aria-hidden=”true” on the wrapper so it does not get announced. If the symbol conveys meaning, remove aria-hidden and add role=”img” with an aria-label such as “Biohazard.” For motion, the @media query shown earlier disables animation for users who prefer reduced motion. Maintain contrast by controlling currentColor on a per-context basis so the icon remains visible against your backgrounds.

Performance

The icon uses borders, transforms, and a single conic-gradient mask on three elements. These operations are inexpensive on modern GPUs and composite efficiently. Avoid heavy box-shadow stacks or filter churn here, since animating those can trigger costly repaint paths. If you need dozens of these icons at once, keep animation off by default and enable it only where it adds value.

Your Icon Lab, No SVGs Required

You built a biohazard symbol entirely with CSS: three masked ring segments, a center ring, and a core dot. The geometry rides on a few custom properties, so you can resize and recolor it instantly.

Use the same ideas to craft other symbols. Start from a circle, a ring, or a simple wedge, then combine masks, clip paths, and transforms. With these building blocks and references like how to make a circle with CSS and a triangle-up with CSS, you now have the tools to assemble your own custom icon set.

Leave a Comment