Editorial Ink
A serif, italic button with a thin frame and a hairline underline that draws in on hover. Press and hold and ink floods the button from your finger; release and it retreats.
Editorial Ink the 11th 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-ink"> <span class="btn-ink-fill" aria-hidden="true"></span> <span class="btn-ink-underline" aria-hidden="true"></span> <span class="btn-ink-label">Discover</span> </button>
.btn-ink {
position: relative;
padding: 14px 32px;
border: none;
background: transparent;
color: #f5f0e8;
font-family: Georgia, "Times New Roman", serif;
font-size: 16px;
font-weight: 700;
font-style: italic;
letter-spacing: -0.5px;
cursor: pointer;
overflow: hidden;
user-select: none;
}
.btn-ink::before {
content: '';
position: absolute;
inset: 0;
border: 1.5px solid rgba(245,240,232,0.2);
transition: border-color 0.3s;
}
.btn-ink:hover::before { border-color: rgba(245,240,232,0.5); }
.btn-ink-underline {
position: absolute;
left: 32px;
right: 32px;
bottom: 10px;
height: 1px;
background: #f5f0e8;
transform: scaleX(0);
transform-origin: left;
transition: transform 0.4s cubic-bezier(.77,0,.18,1);
}
.btn-ink:hover .btn-ink-underline { transform: scaleX(1); }
.btn-ink-fill {
position: absolute;
inset: 0;
z-index: 0;
background: #f5f0e8;
clip-path: circle(0% at var(--ox, 50%) var(--oy, 50%));
}
.btn-ink-label { position: relative; z-index: 1; }
.btn-ink.is-spreading .btn-ink-fill {
clip-path: circle(150% at var(--ox, 50%) var(--oy, 50%));
transition: clip-path 0.6s cubic-bezier(.4,0,.2,1);
}
.btn-ink.is-spreading .btn-ink-label { color: #0c0c0f; transition: color 0.2s 0.15s; }
.btn-ink.is-retreating .btn-ink-fill {
clip-path: circle(0% at var(--ox, 50%) var(--oy, 50%));
transition: clip-path 0.5s cubic-bezier(.4,0,.2,1) 0.05s;
}
@media (prefers-reduced-motion: reduce) {
.btn-ink-fill, .btn-ink-underline { transition: none; }
} document.querySelectorAll('.btn-ink').forEach(function (btn) {
function retreat() {
btn.classList.remove('is-spreading');
btn.classList.add('is-retreating');
setTimeout(function () { btn.classList.remove('is-retreating'); }, 600);
}
btn.addEventListener('mousedown', function (e) {
var r = btn.getBoundingClientRect();
btn.style.setProperty('--ox', ((e.clientX - r.left) / r.width * 100) + '%');
btn.style.setProperty('--oy', ((e.clientY - r.top) / r.height * 100) + '%');
btn.classList.remove('is-retreating');
btn.classList.add('is-spreading');
});
btn.addEventListener('mouseup', retreat);
btn.addEventListener('mouseleave', function () {
if (btn.classList.contains('is-spreading')) retreat();
});
});