Brutalist Glitch
A raw outlined button that fills hard on hover. Click triggers a chromatic-aberration glitch — the label tears into red and cyan layers with a violent shake before snapping back.
Brutalist Glitch the 8th of 43 designs in the 43 CSS Button Designs collection. The design pairs CSS styling with a small amount of JavaScript for interactivity. Copy the HTML, CSS and JavaScript panels below into your project — the JS is self-contained, has zero dependencies, and is safe to drop into any framework (React, Vue, Svelte, plain HTML). The design honours prefers-reduced-motion and uses real semantic markup, so it ships accessibility-ready out of the box.
Live preview
The code
<button class="btn-glitch" data-text="SUBMIT"> <span class="btn-glitch-fill" aria-hidden="true"></span> <span class="btn-glitch-label">SUBMIT</span> </button>
.btn-glitch {
position: relative;
padding: 14px 36px;
border: 2px solid #fff;
background: transparent;
color: #fff;
font-family: "Arial Narrow", Arial, sans-serif;
font-size: 18px;
font-weight: 700;
letter-spacing: 3px;
text-transform: uppercase;
cursor: pointer;
overflow: hidden;
user-select: none;
transition: color 0.1s;
}
.btn-glitch-fill {
position: absolute;
inset: 0;
z-index: 0;
background: #fff;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.15s cubic-bezier(.77,0,.18,1);
}
.btn-glitch-label { position: relative; z-index: 1; }
.btn-glitch:hover { color: #0c0c0f; }
.btn-glitch:hover .btn-glitch-fill { transform: scaleX(1); }
.btn-glitch.is-glitching { animation: btn-glitch-shake 0.4s cubic-bezier(.36,.07,.19,.97) both; }
.btn-glitch.is-glitching::before,
.btn-glitch.is-glitching::after {
content: attr(data-text);
position: absolute;
inset: 0;
z-index: 2;
display: flex;
align-items: center;
justify-content: center;
font-family: "Arial Narrow", Arial, sans-serif;
font-size: 18px;
font-weight: 700;
letter-spacing: 3px;
overflow: hidden;
}
.btn-glitch.is-glitching::before {
color: #ff0040;
clip-path: polygon(0 0, 100% 0, 100% 45%, 0 45%);
animation: btn-glitch-l1 0.4s steps(2) forwards;
}
.btn-glitch.is-glitching::after {
color: #00fff9;
clip-path: polygon(0 55%, 100% 55%, 100% 100%, 0 100%);
animation: btn-glitch-l2 0.4s steps(2) forwards;
}
@keyframes btn-glitch-shake {
0%,100% { transform: translate(0); }
20% { transform: translate(-3px, 1px) skewX(-3deg); }
40% { transform: translate(3px, -1px) skewX(3deg); }
60% { transform: translate(-2px, 2px); }
80% { transform: translate(2px, -2px); }
}
@keyframes btn-glitch-l1 {
0% { transform: translate(-4px, -2px); opacity: 1; }
50% { transform: translate(4px, 2px); opacity: 0.8; }
100% { transform: translate(0); opacity: 0; }
}
@keyframes btn-glitch-l2 {
0% { transform: translate(4px, 2px); opacity: 1; }
50% { transform: translate(-4px, -2px); opacity: 0.8; }
100% { transform: translate(0); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
.btn-glitch.is-glitching { animation: none; }
.btn-glitch.is-glitching::before,
.btn-glitch.is-glitching::after { animation: none; opacity: 0; }
} document.querySelectorAll('.btn-glitch').forEach(function (btn) {
btn.addEventListener('click', function () {
btn.classList.add('is-glitching');
setTimeout(function () { btn.classList.remove('is-glitching'); }, 420);
});
});