<!-- default button -->
<button>
Button
</button>
<!-- secondary button, with border and transparent background -->
<button style="--background: none; --color: hsl(265, 100%, 48%); --border: hsl(265, 100%, 48%);">
Button
</button>
<!-- tertiary button, no border, no background -->
<button style="--background: none; --color: hsl(265, 100%, 48%);">
Button
</button>
<!-- icon button -->
<button>
<svg viewBox="-50 -37.5 100 75" width="20" height="15">
<g fill="currentColor" stroke="currentColor" stroke-width="10" stroke-linecap="round" stroke-linejoin="round">
<path d="M -25 32.5 l 25 -65 25 65 -60 -42 70 0z" />
</g>
</svg>
Button
</button>
@import url("https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap");
*,
*:before,
*:after {
box-sizing: border-box;
padding: 0;
margin: 0;
}
/* display the buttons in the center of the viewport, one above the other */
body {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: hsl(0, 0%, 5%);
background: hsl(0, 0%, 98%);
font-family: "Roboto", sans-serif;
}
/* separate the buttons from the preceding elements */
body > * + * {
margin-block-start: 2.5rem;
}
/* each button has the following properties
- top, left, size for the active state
- background, color, border for the color palette
*/
button {
--top: 50%;
--left: 50%;
--size: 100%;
--background: hsl(265, 100%, 50%);
--color: hsl(265, 100%, 100%);
/* :p */
--border: none;
color: var(--color);
background: var(--background);
border: 1px solid var(--border);
font-family: inherit;
padding: 0.6rem 1.25rem;
font-size: 0.9rem;
border-radius: 5px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 1px;
/* overflow and position matching the pseudo elements */
overflow: hidden;
position: relative;
/* remove the default outline
! substituted with the pseudo elements
*/
outline: none;
}
/* if existing, separate the inline icon from the text which follows */
button svg {
width: 1em;
height: auto;
display: inline-block;
margin-inline-end: 0.2rem;
}
/* with a pseudo element add a semitransparent layer with the same color of the text */
button:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: currentColor;
border-radius: inherit;
opacity: 0;
}
button:focus:before,
button:hover:before {
opacity: 0.1;
}
/* with another pseudo element add a semitransparent layer clipped to be a circle */
button:after {
content: "";
position: absolute;
top: var(--top);
left: var(--left);
transform: translate(-50%, -50%);
width: var(--size);
height: var(--size);
background: currentColor;
clip-path: circle(0%);
opacity: 0.3;
border-radius: inherit;
}
/*
! add the transition only as the active state is "enabled"
! add a negative delay for the clip to already start from a small circle
! add a delay for the opacity to follow the clip-path
*/
button:active:after {
clip-path: circle(100%);
opacity: 0;
transition: clip-path 0.5s cubic-bezier(0.55, 0.085, 0.68, 0.53), opacity 0.4s ease-in-out;
transition-delay: -0.1s, 0.5s;
}
// to have the pseudo element expand from where the cursor "hits" the button
// update the custom properties according to the precise coordinate
const buttons = document.querySelectorAll('button');
function handleClick(e) {
const { layerX, layerY } = e;
const { width, height } = this.getBoundingClientRect();
this.style.setProperty('--top', `${(layerY / height) * 100}%`);
this.style.setProperty('--left', `${(layerX / width) * 100}%`);
// for the size consider the distance from the farthest angle
const dx = layerX > width / 2 ? layerX : width - layerX;
const dy = layerY > height / 2 ? layerY : height - layerY;
const size = Math.sqrt(dx ** 2 + dy ** 2) * 2;
this.style.setProperty('--size', `${size}px`);
}
buttons.forEach(button => {
button.addEventListener('mousedown', handleClick);
});
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.