A gear icon is the universal Settings symbol. You can ship it as an image or SVG, but building it in pure CSS gives you theme control, sharp rendering at any size, and zero network requests. By the end of this project, you will have a compact, scalable gear built with a single element, two pseudo-elements, and a small set of CSS variables.
Why Project: Build a Simple “Settings” Gear Icon with CSS Matters
CSS-only icons slot into any design system and inherit color, size, and motion rules from your stylesheet. No asset pipeline, no external dependencies, and no font loading delays. A CSS gear also scales cleanly on high-DPI screens without hinting artifacts. When you need quick variations, a few variable tweaks handle size, tooth count (in our case, eight teeth), and animation speed. If you prefer vector precision, SVG is great; this method shines when you want light, themeable UI icons that feel native to your CSS stack.
Prerequisites
You do not need a graphics tool for this. The gear uses a circular ring, rectangular teeth, and two pseudo-elements. A little transform math does the rest.
- Basic HTML
- CSS custom properties
- CSS pseudo-elements (::before / ::after)
Step 1: The HTML Structure
The final markup keeps the icon accessible inside a button. The gear itself is one span. The inner content is handled completely in CSS, so the HTML stays minimal and reusable.
<!-- HTML -->
<button class="settings-btn" aria-label="Open settings">
<span class="gear" aria-hidden="true"></span>
</button>
The button carries the accessible name, and the gear span is marked as decorative with aria-hidden. This mirrors how you would use the icon in a real UI. You can drop the same .gear span anywhere outside a button if you only need the symbol.
Step 2: The Basic CSS & Styling
Start with sensible defaults, define a few variables for sizing, and style the button wrapper so the icon sits in a predictable box. The .gear will be sized by a single –size variable, and all other measurements derive from it for consistent proportions.
/* CSS */
:root {
--size: 72px; /* overall gear size */
--gear-color: #2d3a46; /* icon color */
--ring-thickness: calc(var(--size) * 0.18);
--tooth-length: calc(var(--size) * 0.28);
--tooth-width: calc(var(--size) * 0.18);
--connect: 2px; /* small overlap so teeth meet the ring cleanly */
--spin-speed: 900ms; /* used later for animation */
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html, body {
height: 100%;
}
body {
margin: 0;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
display: grid;
place-items: center;
background: #0e1217;
color: #e8eef5;
}
.settings-btn {
appearance: none;
border: 0;
background: #141b23;
color: inherit;
padding: 14px;
border-radius: 10px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 10px;
transition: background 160ms ease;
}
.settings-btn:hover {
background: #19222c;
}
.settings-btn:active {
background: #10161d;
}
/* Base gear box */
.gear {
position: relative;
display: inline-block;
width: var(--size);
height: var(--size);
border-radius: 50%;
}
Advanced Tip: Tie every dimension to –size so the icon stays balanced at any scale. You can theme color and animation speed with variables, and even override them per component using a wrapper class or inline style.
Step 3: Building the Gear Ring
The gear body is a circular ring with a transparent center. A radial-gradient draws the ring without extra elements. The gradient stays crisp at any size and does not require masking tricks.
/* CSS */
.gear {
/* keep base geometry from Step 2 */
background:
radial-gradient(closest-side,
transparent calc(100% - var(--ring-thickness) - 0.5px),
var(--gear-color) 0
);
/* circle already set by border-radius in Step 2 */
}
How This Works (Code Breakdown)
The radial-gradient uses closest-side so 100% equals the outer radius of the .gear box. The color stop at 100% – var(–ring-thickness) creates a transparent hole from the center up to the inner edge of the ring. The 0.5px nudge helps avoid anti-alias seams on some displays. From that point to 100%, the gradient paints var(–gear-color), which gives you the outer ring.
The shape becomes a perfect disc due to border-radius: 50%. If you want a refresher on building circles with CSS alone, see this quick reference on how to make a circle with CSS. That same trick underpins the icon’s body here.
Step 4: Building the Teeth
The teeth are the fun part. We draw a single rectangular tooth on a pseudo-element and clone it into eight positions using box-shadow. Then we rotate a second pseudo-element by 45 degrees and repeat the clone list, which yields the diagonals. This keeps the DOM lean and gives you eight evenly spaced teeth without extra markup.
/* CSS */
.gear::before,
.gear::after {
content: "";
position: absolute;
left: 50%;
top: 50%;
width: var(--tooth-width);
height: var(--tooth-length);
background: var(--gear-color);
transform: translate(-50%, -50%);
border-radius: 2px;
}
/* Cardinal directions via 4 shadows */
.gear::before {
/* center tooth (hidden by overlap), plus 4 clones */
box-shadow:
/* top */
0 calc(-1 * ( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) )) 0 0 var(--gear-color),
/* right */
calc( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) ) 0 0 0 var(--gear-color),
/* bottom */
0 calc( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) ) 0 0 var(--gear-color),
/* left */
calc(-1 * ( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) )) 0 0 0 var(--gear-color);
}
/* Diagonals: rotate 45deg and repeat shadows */
.gear::after {
transform: translate(-50%, -50%) rotate(45deg);
box-shadow:
0 calc(-1 * ( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) )) 0 0 var(--gear-color),
calc( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) ) 0 0 0 var(--gear-color),
0 calc( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) ) 0 0 var(--gear-color),
calc(-1 * ( (var(--size) / 2) + (var(--tooth-length) / 2) - var(--connect) )) 0 0 0 var(--gear-color);
}
How This Works (Code Breakdown)
Each pseudo-element is a small rectangle that lives at the center of the .gear box. The translate(-50%, -50%) move locks its center to the .gear center. Rather than absolutely positioning eight rectangles by hand, we offset clones using box-shadow. A box-shadow with zero blur and zero spread essentially paints a duplicate layer of the same rectangle at the given x, y offset.
The offset math pushes each clone so its center sits just beyond the outer radius. That distance is half the gear size plus half the tooth length, minus a small connect overlap. The overlap forces the tooth to tuck into the ring by a couple of pixels so you never see a hairline gap when backgrounds change.
The ::before set covers the four cardinal directions. The ::after set adds rotate(45deg) to the same pattern, which produces the diagonals while keeping the clones balanced. If you want to study the base rectangle shape itself, the technique parallels the tutorial on how to make a rectangle with CSS. The gear ends up as a circle with eight attached rectangles, which keeps the mental model simple.
Advanced Techniques: Adding Animations & Hover Effects
Rotation is the classic effect for a settings icon. Spin the gear on hover or while a preferences panel loads. Keep it subtle so it reads as feedback rather than spectacle.
/* CSS */
/* Subtle hover spin */
.settings-btn:hover .gear {
animation: spin var(--spin-speed) linear infinite;
}
/* Active press: scale down slightly */
.settings-btn:active .gear {
transform: scale(0.96);
}
/* Keyframes */
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Respect motion preferences */
@media (prefers-reduced-motion: reduce) {
.settings-btn:hover .gear,
.settings-btn:active .gear {
animation: none !important;
transform: none !important;
}
}
The hover rule applies an infinite linear spin with a configurable speed. Because transform is a composited property, the animation stays smooth on modern browsers with minimal paint cost. The active state adds a tiny scale to give click feedback for button usage. The prefers-reduced-motion query disables both for users who opt out of motion.
Accessibility & Performance
Icons often end up as decoration, but a settings button is interactive and must be labeled correctly. The example structure handles both use cases with a tiny markup change if needed.
Accessibility
When the icon is part of a button, use an accessible name on the button (aria-label or visible text) and hide the decorative span with aria-hidden=”true”. If you are placing the gear alone to convey meaning, give the span a role and a label instead, for example role=”img” aria-label=”Settings”. Only use one label. Avoid redundant announcements by not labeling both parent and child.
Respect motion preferences. The prefers-reduced-motion rule in the animation step disables rotation for users who request less motion. This keeps the UI steady and inclusive.
Performance
This approach is fast. The gear ring uses a single radial-gradient, which renders on the GPU-backed compositing path. The teeth are drawn with two pseudo-elements and eight total box-shadow clones. They are static layers with no blur, so they do not trigger heavy paints. The hover animation rotates the element with transform, which remains on the compositor. Keep the tooth count at eight for this simplified clone method; if you need many more teeth with variable spacing, consider an SVG path or a CSS mask built with a conic-gradient mask-image.
If you want to pair the gear with other UI symbols built entirely in CSS, you can reuse techniques from shapes like a CSS circle for bases or the CSS rectangle for spokes and bars. Building a small set this way keeps everything crisp and themeable without extra files.
Sharpen your CSS icon toolkit
You built a responsive, themeable gear icon with a single element and a few gradients, transforms, and shadows. You can scale it, recolor it, and animate it with a couple of variables. Apply the same patterns to other interface symbols and wire up your own CSS icon set that matches your design system without a separate asset pipeline.