Why rebuild a logo in CSS?
An SVG would have been a perfectly sensible way to put our logo on the site. But it’s made of repeating triangles, and we wanted to see what we could do with those pieces in CSS.
Every triangle has the same shape. Its position, direction, and opacity make the difference. That gives us a small set of rules for drawing the “L,” changing its color, and moving the pieces around.
It’s the logo you see in the header and in the tool below. CSS handles the drawing and burst animation. JavaScript handles the color controls and rearranges the triangles on hover or click.
Start with one reusable triangle
The markup is deliberately plain. A wrapper establishes the coordinate system, and each empty element becomes one facet.
<span class="brand-mark" aria-hidden="true">
<i class="brand-facet brand-facet-1"></i>
<i class="brand-facet brand-facet-2"></i>
<i class="brand-facet brand-facet-3"></i>
<!-- 14 more facets -->
</span>
The visible triangle comes from clip-path. Its fill uses currentColor, so the entire logo can change color from one property on its parent.
.brand-mark {
position: relative;
width: calc(2.15rem * 288 / 376);
height: 2.15rem;
color: #45c3e8;
}
.brand-facet {
position: absolute;
width: 33.333%;
height: 25%;
background: currentColor;
clip-path: polygon(50% 0, 100% 100%, 0 100%);
}
Downward-facing facets use the same box with the points reversed.
.brand-facet-down {
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
This is still ordinary HTML and CSS. There is no canvas, inline SVG drawing, icon font, or remote asset request.
Reconstruct the grid
We measured the supplied source artwork as a grid of congruent triangles. The finished mark uses 17 facets distributed across four rows. Each facet receives a grid position and the opacity sampled from the source.
.brand-facet-1 {
left: 33.333%;
top: 0;
opacity: 0.851;
}
.brand-facet-2 {
left: 50%;
top: 0;
opacity: 0.898;
clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.brand-facet-3 {
left: 66.667%;
top: 0;
opacity: 0.349;
}
Using opacity instead of 17 separate colors is important. The icon can be white over a photograph, ink over aqua, or blue over an off-white page while preserving the relationships between facets.
CSS triangles can expose hairline seams when percentage coordinates land between physical pixels. We corrected the most visible row boundary with a quarter-pixel overlap. It is small enough to cover the rasterization seam without creating a noticeably darker edge where translucent layers overlap.
Separate every facet evenly
Our first hover experiment translated alternating triangles in opposite directions. It looked energetic, but the gaps were inconsistent. Some edges opened widely while others barely moved.
Scaling each triangle around its geometric centroid produces a much cleaner result. For an upward triangle, the centroid sits two-thirds of the way down its box. For a downward triangle, it sits one-third of the way down.
.brand-facet-up {
transform-origin: 50% 66.667%;
}
.brand-facet-down {
transform-origin: 50% 33.333%;
}
.brand.is-bursting .brand-facet {
animation: brand-facet-burst 720ms cubic-bezier(.2, .8, .2, 1);
}
@keyframes brand-facet-burst {
0%, 100% { transform: none; }
42% {
transform:
translate(var(--burst-x), var(--burst-y))
rotate(var(--burst-rotate))
scale(0.9);
}
}
Because the facets are congruent, scaling all of them by the same amount creates consistent gutters along their shared edges. We then calculate a small offset from each triangle’s current position: every facet falls slightly, while facets farther from the center travel and rotate more. The full mark also grows and tilts, making the interaction visible even when the logo is small in the header.
Let similar facets trade places
As the mark expands on hover—or when it is clicked—the triangles move into new grid positions and then settle into the intact silhouette. We restrict each swap group in two ways: triangles must share an orientation, and their opacity values must be similar.
const facetSwapGroups = [
[1, 4, 10],
[2, 5, 9, 11],
[3, 6, 12, 16],
[7, 13, 15, 17],
];
That constraint matters. An unrestricted shuffle preserves the outer silhouette but can erase the tonal “L.” Keeping dark facets with other dark facets—and pale facets with other pale facets—allows the composition to change while the logo remains recognizable.
The facets animate their left and top values for this small, bounded interaction. There are only 17 absolutely positioned elements, no continuous loop, and no work while the logo is idle.
Build the color selector with the same component
The standalone icon preview and the tool embedded in this article render the same Astro component. Each button updates a data attribute and its accessible pressed state.
button.addEventListener("click", () => {
preview.dataset.color = button.dataset.iconColor;
preview.querySelectorAll("[data-icon-color]").forEach((option) => {
option.setAttribute("aria-pressed", String(option === button));
});
});
CSS custom properties keep the icon, background, controls, and text coordinated for each treatment.
.icon-preview[data-color="white"] {
--preview-background: #075578;
--preview-color: #fff;
--preview-ink: #fff;
}
.icon-preview[data-color="blue"] {
--preview-background: #f4f6f5;
--preview-color: #45c3e8;
--preview-ink: #10232f;
}
Motion should remain optional
Brand animation is decoration, not a requirement for understanding or navigation. When a visitor prefers reduced motion, the icon stays intact and the swapping script does not run.
@media (prefers-reduced-motion: reduce) {
.brand-mark,
.brand-facet {
transition: none;
animation: none !important;
}
}
A useful test, not a universal rule
Admittedly, this was a relatively simple test because the logo is composed entirely of equal geometric shapes. That regularity gave us a predictable grid, shared proportions, and a constrained set of positions to animate. A more organic, illustrative, or typographic mark would usually be better served by a carefully optimized SVG.
Pure CSS is not automatically faster, more accessible, or easier to maintain simply because it avoids an image file. In this case, the technique earned its place because the underlying geometry was simple, the finished code stayed small, and the same facets could support color changes and motion without another drawing system.
That leaves us with a responsive logo built from a few small elements, a focused stylesheet, and a minimal event handler. It inherits color naturally, requires no additional image request, and gives us motion that belongs to the geometry of the mark rather than sitting on top of it.
Change the code. See what happens.
Edit either panel to update the isolated preview. Hover, focus, or click the logo to see its current burst-and-reshuffle animation. The animation script is fixed; only HTML and CSS are editable.