Share Button
Press Share and the options slide out from behind it, the last one pushing the pill open, while the arrow takes off and a cross draws itself. Copy link turns into a check; X and LinkedIn open in a small window.
View codeA round phone icon that opens into a full "Book a call" button, with a little ring. Stays open on touch screens. Pure CSS.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Add the Kodza Call Button to my contact section.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<a class="kodza-call-button" href="tel:+33100000000">
<span class="kodza-call-button__icon" aria-hidden="true">
<svg viewBox="0 0 24 24"><path d="M6.6 3.5h2.6l1.4 4-1.9 1.3a11 11 0 0 0 6.5 6.5l1.3-1.9 4 1.4v2.6a2 2 0 0 1-2.2 2A16.5 16.5 0 0 1 4.6 5.7a2 2 0 0 1 2-2.2z" /></svg>
</span>
<span class="kodza-call-button__label"><span>Book a call</span></span>
</a>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Call Button
A round icon that opens into a full button: the label slides out from behind the icon,
and the phone gives a little ring. On touch screens, where nothing hovers, it stays open.
Pure CSS, tuned with the --kz-call-* variables. */
.kodza-call-button {
--kz-call-bg: #0a0a0a;
--kz-call-fg: #ffffff;
--kz-call-size: 52px;
--kz-call-duration: 0.6s;
--kz-call-ease: cubic-bezier(0.22, 1, 0.36, 1);
display: inline-grid;
grid-template-columns: var(--kz-call-size) 0fr;
align-items: center;
height: var(--kz-call-size);
border-radius: 999px;
background: var(--kz-call-bg);
color: var(--kz-call-fg);
font: inherit;
font-weight: 500;
letter-spacing: -0.01em;
text-decoration: none;
white-space: nowrap;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
transition: grid-template-columns var(--kz-call-duration) var(--kz-call-ease), transform 0.25s var(--kz-call-ease);
}
.kodza-call-button__icon {
display: grid;
place-items: center;
}
.kodza-call-button__icon svg {
width: 20px;
height: 20px;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linejoin: round;
transform-origin: 50% 60%;
}
/* The label column opens from 0 to its natural width */
.kodza-call-button__label {
min-width: 0;
overflow: hidden;
}
.kodza-call-button__label > span {
display: block;
padding-right: 24px;
opacity: 0;
transform: translateX(-12px);
transition:
opacity calc(var(--kz-call-duration) * 0.5) ease,
transform var(--kz-call-duration) var(--kz-call-ease);
}
.kodza-call-button:hover,
.kodza-call-button:focus-visible {
grid-template-columns: var(--kz-call-size) 1fr;
}
.kodza-call-button:hover .kodza-call-button__label > span,
.kodza-call-button:focus-visible .kodza-call-button__label > span {
opacity: 1;
transform: none;
transition-delay: calc(var(--kz-call-duration) * 0.15);
}
.kodza-call-button:hover .kodza-call-button__icon svg,
.kodza-call-button:focus-visible .kodza-call-button__icon svg {
animation: kodza-call-ring 0.9s ease-in-out;
}
.kodza-call-button:active {
transform: scale(0.97);
}
.kodza-call-button:focus-visible {
outline: 2px solid var(--kz-call-bg);
outline-offset: 3px;
}
@keyframes kodza-call-ring {
0%, 100% { transform: rotate(0); }
12% { transform: rotate(-16deg); }
24% { transform: rotate(14deg); }
36% { transform: rotate(-11deg); }
48% { transform: rotate(8deg); }
60% { transform: rotate(-4deg); }
72% { transform: rotate(0); }
}
/* Touch screens: nothing hovers, so the button shows its label from the start */
@media (hover: none) {
.kodza-call-button {
grid-template-columns: var(--kz-call-size) 1fr;
}
.kodza-call-button__label > span {
opacity: 1;
transform: none;
}
}
@media (prefers-reduced-motion: reduce) {
.kodza-call-button,
.kodza-call-button__label > span {
transition-duration: 0.01ms !important;
transition-delay: 0s !important;
}
.kodza-call-button__icon svg {
animation: none !important;
}
}
/* Theming example — on a dark background:
.dark-section .kodza-call-button {
--kz-call-bg: #ffffff;
--kz-call-fg: #0a0a0a;
}
*/
/* kodza-guard 67d713270725 — generated by Kodza, do not edit.
Theme guard: keeps a site's own CSS (WordPress themes, Bootstrap, CSS resets…) off this
component. :not(#_) is always true and weighs as much as an id, so these lines win over
theme rules made of classes and tags. Customise the component through its --kz-* variables. */
/* What the browser gives, where the component sets it only in some states: at the lowest weight, so its own rules below put it back */
:where(.kodza-call-button):not(#_) {
outline: revert;
outline-offset: revert;
}
/* What the browser gives, on everything the component leaves to it */
.kodza-call-button:not(#_) {
border: revert;
padding: revert;
list-style: revert;
box-sizing: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-height: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-call-button__icon:not(#_),
.kodza-call-button__label:not(#_),
.kodza-call-button__label span:where(:not([class*="kodza-"])):not(#_) {
box-sizing: revert;
}
.kodza-call-button__icon svg:where(:not([class*="kodza-"])):not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
background-color: revert;
background-image: revert;
box-shadow: revert;
outline-offset: revert;
appearance: revert;
max-width: revert;
max-height: revert;
color: revert;
font-family: revert;
font-size: revert;
font-weight: revert;
font-style: revert;
line-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-call-button__icon svg:where(:not([class*="kodza-"])):not(#_):where(:not([hidden])) {
display: revert;
}
.kodza-call-button__icon svg:where(:not([class*="kodza-"])):not(#_):where([hidden]) {
display: none;
}
/* The component's own rules, one step stronger: real buttons and fields, what it sets on tags themes style (links, lists, headings, images…), and the root width (a theme may narrow it, never widen it) */
.kodza-call-button:not(#_) {
display: inline-grid;
border-radius: 999px;
background: var(--kz-call-bg);
color: var(--kz-call-fg);
font: inherit;
font-weight: 500;
letter-spacing: -0.01em;
text-decoration: none;
transition: grid-template-columns var(--kz-call-duration) var(--kz-call-ease), transform 0.25s var(--kz-call-ease);
}
.kodza-call-button:focus-visible:not(#_) {
outline: 2px solid var(--kz-call-bg);
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.kodza-call-button:not(#_) {
transition-duration: 0.01ms !important;
transition-delay: 0s !important;
}
}
'use client';
// Kodza — Call Button, for React. From https://kodza.dev/components/call-button-expand/
// Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license
// Next to this file: call-button-expand.css, the component's CSS from Kodza, as they are.
// Yours to edit: the markup is plain JSX below.
import { useRef, type ComponentPropsWithoutRef, type ComponentRef, type CSSProperties } from 'react';
import './call-button-expand.css';
export type CallButtonProps = Omit<ComponentPropsWithoutRef<'a'>, "children" | "vars"> & {
/** Settings, as CSS variables on the root */
vars?: {
/** default: #0a0a0a */
"--kz-call-bg"?: string | number;
/** default: #ffffff */
"--kz-call-fg"?: string | number;
/** default: 52px */
"--kz-call-size"?: string | number;
/** default: 0.6s */
"--kz-call-duration"?: string | number;
/** default: cubic-bezier(0.22, 1, 0.36, 1) */
"--kz-call-ease"?: string | number;
};
/** The text */
children?: string;
className?: string;
};
export function CallButton({ vars, children = "Book a call", className, style, ...rest }: CallButtonProps) {
const ref = useRef<ComponentRef<'a'>>(null);
const classes = ["kodza-call-button", className].filter(Boolean).join(' ');
// Everything the markup is made of: when it changes, React gets a fresh element (see the top)
const key = JSON.stringify([classes, vars, children, style, rest]);
return (
<a href="tel:+33100000000" key={key} {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<span className="kodza-call-button__icon" aria-hidden="true">
<svg viewBox="0 0 24 24">
<path d="M6.6 3.5h2.6l1.4 4-1.9 1.3a11 11 0 0 0 6.5 6.5l1.3-1.9 4 1.4v2.6a2 2 0 0 1-2.2 2A16.5 16.5 0 0 1 4.6 5.7a2 2 0 0 1 2-2.2z" />
</svg>
</span>
{' '}
<span className="kodza-call-button__label">
<span>{children}</span>
</span>
</a>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.