Alert
A message in the page: note, success, warning or error, told apart by the icon, not by color. The icon draws itself, a dismissed alert folds away and an error unfolds its log.
View codeWhat a list shows when it is empty: a CSS drawing that answers when you come near, a line in a human voice and the next step. A second take for searches that find nothing.
With your AI
Say it to your AI. It gets the exact code below, not its best guess, and knows where it goes.
Show a Kodza Empty State when my search finds nothing.
claude mcp add -t http kodza https://kodza.dev/api/mcp
<div class="kodza-empty">
<div class="kodza-empty__art" aria-hidden="true">
<span class="kodza-empty__card"></span><span class="kodza-empty__card"></span><span class="kodza-empty__card"><svg viewBox="0 0 16 16"><path d="M8 4v8M4 8h8" /></svg></span>
</div>
<h3 class="kodza-empty__title">Nothing here yet</h3>
<p class="kodza-empty__text">Your collection is as empty as a Monday standup. Save a component and it lands here.</p>
<div class="kodza-empty__actions">
<a class="kodza-empty__button" href="#components">Browse components</a>
<button class="kodza-empty__button kodza-empty__button--quiet" type="button">Import a list</button>
</div>
</div>
<div class="kodza-empty kodza-empty--search">
<div class="kodza-empty__art" aria-hidden="true">
<span class="kodza-empty__lines"><i></i><i></i><i></i></span>
<svg class="kodza-empty__lens" viewBox="0 0 32 32"><circle cx="13" cy="13" r="8.5" /><path d="M19.5 19.5 26 26" /></svg>
</div>
<h3 class="kodza-empty__title">No results for “center a div”</h3>
<p class="kodza-empty__text">Even the internet is still working on that one. Fewer words help, or try:</p>
<div class="kodza-empty__actions">
<button class="kodza-empty__chip" type="button">flexbox</button>
<button class="kodza-empty__chip" type="button">grid</button>
<button class="kodza-empty__chip" type="button">place-items</button>
</div>
</div>
/* Kodza License: free to use, client work included. Not to resell or redistribute, ported or not: https://kodza.dev/docs/#print-license */
/* Kodza — Empty State
What a list shows when there is nothing in it: a small drawing in CSS, a title, a line in a
human voice and what to do next. The drawing answers when you come near: the blank cards
fan out, or the lens sweeps the empty results (--search). No script.
Everything is tuned with the --kz-empty-* variables. */
.kodza-empty {
--kz-empty-bg: transparent;
--kz-empty-fg: #0a0a0a;
--kz-empty-muted: rgba(10, 10, 10, 0.62);
--kz-empty-line: rgba(10, 10, 10, 0.18);
--kz-empty-soft: rgba(10, 10, 10, 0.06);
--kz-empty-card: #ffffff;
--kz-empty-ink: #0a0a0a;
--kz-empty-on-ink: #ffffff;
--kz-empty-ease: cubic-bezier(0.22, 1, 0.36, 1);
display: grid;
box-sizing: border-box;
justify-items: center;
padding: 48px 32px 44px;
border: 1px dashed var(--kz-empty-line);
border-radius: 16px;
background: var(--kz-empty-bg);
color: var(--kz-empty-fg);
text-align: center;
}
/* The drawing: three blank cards, the front one with a plus */
.kodza-empty__art {
position: relative;
display: grid;
place-items: center;
width: 140px;
height: 104px;
margin-bottom: 22px;
}
.kodza-empty__card {
position: absolute;
display: grid;
place-items: center;
width: 60px;
height: 76px;
border: 1.5px dashed var(--kz-empty-line);
border-radius: 10px;
background: var(--kz-empty-card);
transition: transform 0.6s var(--kz-empty-ease), border-color 0.3s ease;
}
.kodza-empty__card:nth-child(1) {
transform: translateX(-14px) rotate(-9deg);
}
.kodza-empty__card:nth-child(2) {
transform: translateX(14px) rotate(9deg);
}
.kodza-empty__card:nth-child(3) {
border-style: solid;
border-color: var(--kz-empty-fg);
box-shadow: 0 10px 24px -14px rgba(10, 10, 10, 0.4);
}
.kodza-empty__card svg {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 1.6;
stroke-linecap: round;
transition: transform 0.6s var(--kz-empty-ease);
}
.kodza-empty:hover .kodza-empty__card:nth-child(1),
.kodza-empty:focus-within .kodza-empty__card:nth-child(1) {
transform: translateX(-38px) translateY(4px) rotate(-16deg);
}
.kodza-empty:hover .kodza-empty__card:nth-child(2),
.kodza-empty:focus-within .kodza-empty__card:nth-child(2) {
transform: translateX(38px) translateY(4px) rotate(16deg);
}
.kodza-empty:hover .kodza-empty__card:nth-child(3),
.kodza-empty:focus-within .kodza-empty__card:nth-child(3) {
transform: translateY(-6px);
}
.kodza-empty:hover .kodza-empty__card svg,
.kodza-empty:focus-within .kodza-empty__card svg {
transform: rotate(90deg);
}
/* Search: lines of results that are not there, and a lens */
.kodza-empty__lines {
display: grid;
gap: 10px;
width: 116px;
}
.kodza-empty__lines i {
height: 10px;
border-radius: 999px;
background: var(--kz-empty-soft);
}
.kodza-empty__lines i:nth-child(2) {
width: 78%;
}
.kodza-empty__lines i:nth-child(3) {
width: 52%;
}
.kodza-empty__lens {
position: absolute;
top: 18px;
left: 66px;
width: 52px;
height: 52px;
fill: var(--kz-empty-card);
fill-opacity: 0.55;
stroke: var(--kz-empty-fg);
stroke-width: 1.8;
stroke-linecap: round;
transition: transform 0.6s var(--kz-empty-ease);
}
.kodza-empty--search:hover .kodza-empty__lens,
.kodza-empty--search:focus-within .kodza-empty__lens {
animation: kodza-empty-scan 1.4s cubic-bezier(0.65, 0, 0.35, 1);
}
@keyframes kodza-empty-scan {
35% {
transform: translate(-58px, -6px) rotate(-8deg);
}
70% {
transform: translate(-30px, 18px) rotate(4deg);
}
}
.kodza-empty__title {
margin: 0;
font-size: 20px;
font-weight: 600;
line-height: 1.25;
letter-spacing: -0.025em;
text-wrap: balance;
}
.kodza-empty__text {
max-width: 34ch;
margin: 8px 0 0;
color: var(--kz-empty-muted);
font-size: 15px;
line-height: 1.55;
text-wrap: pretty;
}
.kodza-empty__actions {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
margin-top: 22px;
}
.kodza-empty__button {
display: inline-flex;
align-items: center;
height: 40px;
padding: 0 18px;
border: 0;
border-radius: 999px;
background: var(--kz-empty-ink);
color: var(--kz-empty-on-ink);
font: inherit;
font-size: 14px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: transform 0.2s ease, background-color 0.2s ease;
}
.kodza-empty__button--quiet {
background: var(--kz-empty-soft);
color: var(--kz-empty-fg);
}
.kodza-empty__button:active,
.kodza-empty__chip:active {
transform: scale(0.97);
}
.kodza-empty__chip {
height: 32px;
padding: 0 12px;
border: 0;
border-radius: 999px;
background: var(--kz-empty-soft);
color: var(--kz-empty-fg);
font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
font-size: 13px;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.kodza-empty__chip:hover {
background: var(--kz-empty-ink);
color: var(--kz-empty-on-ink);
}
.kodza-empty__button:focus-visible,
.kodza-empty__chip:focus-visible {
outline: 2px solid var(--kz-empty-fg);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.kodza-empty__card,
.kodza-empty__card svg,
.kodza-empty__lens,
.kodza-empty__button,
.kodza-empty__chip {
transition-duration: 0.01ms !important;
}
.kodza-empty--search:hover .kodza-empty__lens,
.kodza-empty--search:focus-within .kodza-empty__lens {
animation: none;
}
}
/* Theming example — on a dark background:
.dark-section .kodza-empty {
--kz-empty-fg: #f2f2f0;
--kz-empty-muted: rgba(242, 242, 240, 0.62);
--kz-empty-line: rgba(255, 255, 255, 0.2);
--kz-empty-soft: rgba(255, 255, 255, 0.08);
--kz-empty-card: #161616;
--kz-empty-ink: #f2f2f0;
--kz-empty-on-ink: #0a0a0a;
}
*/
/* kodza-guard 49a7043fb1ad — 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-empty__button):not(#_),
:where(.kodza-empty__chip):not(#_) {
outline: revert;
outline-offset: revert;
}
/* What the browser gives, on everything the component leaves to it */
.kodza-empty__art:not(#_),
.kodza-empty__card:not(#_),
.kodza-empty__actions:not(#_),
.kodza-empty__lines:not(#_) {
box-sizing: revert;
}
.kodza-empty__title:not(#_) {
border: 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-style: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-empty__text:not(#_) {
border: 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-height: revert;
font-family: revert;
font-weight: revert;
font-style: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
}
.kodza-empty__button:not(#_) {
margin: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: revert;
letter-spacing: revert;
text-transform: revert;
text-underline-offset: revert;
text-align: revert;
text-indent: revert;
text-shadow: revert;
vertical-align: revert;
white-space: revert;
min-height: revert;
}
.kodza-empty__lens:not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: 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-empty__chip:not(#_) {
margin: revert;
text-decoration: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: revert;
box-shadow: revert;
appearance: revert;
max-width: revert;
max-height: 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;
white-space: revert;
min-height: revert;
}
.kodza-empty__card svg:where(:not([class*="kodza-"])):not(#_) {
border: revert;
margin: revert;
padding: revert;
border-radius: revert;
outline: revert;
text-decoration: 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-empty__lines i:where(:not([class*="kodza-"])):not(#_) {
border: revert;
margin: revert;
padding: revert;
outline: revert;
text-decoration: revert;
transition: revert;
list-style: revert;
box-sizing: revert;
float: revert;
position: 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-empty__title:not(#_):where(:not([hidden])),
.kodza-empty__text:not(#_):where(:not([hidden])),
.kodza-empty__lens:not(#_):where(:not([hidden])),
.kodza-empty__chip:not(#_):where(:not([hidden])),
.kodza-empty__card svg:where(:not([class*="kodza-"])):not(#_):where(:not([hidden])),
.kodza-empty__lines i:where(:not([class*="kodza-"])):not(#_):where(:not([hidden])) {
display: revert;
}
.kodza-empty__title:not(#_):where([hidden]),
.kodza-empty__text:not(#_):where([hidden]),
.kodza-empty__lens:not(#_):where([hidden]),
.kodza-empty__chip:not(#_):where([hidden]),
.kodza-empty__card svg:where(:not([class*="kodza-"])):not(#_):where([hidden]),
.kodza-empty__lines i: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-empty__card svg:not(#_) {
transition: transform 0.6s var(--kz-empty-ease);
}
.kodza-empty__lines i:not(#_) {
border-radius: 999px;
background: var(--kz-empty-soft);
}
.kodza-empty__lens:not(#_) {
position: absolute;
transition: transform 0.6s var(--kz-empty-ease);
}
.kodza-empty__title:not(#_) {
margin: 0;
font-size: 20px;
font-weight: 600;
line-height: 1.25;
letter-spacing: -0.025em;
}
.kodza-empty__text:not(#_) {
max-width: 34ch;
margin: 8px 0 0;
color: var(--kz-empty-muted);
font-size: 15px;
line-height: 1.55;
}
.kodza-empty__button:not(#_) {
display: inline-flex;
align-items: center;
height: 40px;
padding: 0 18px;
border: 0;
border-radius: 999px;
background: var(--kz-empty-ink);
color: var(--kz-empty-on-ink);
font: inherit;
font-size: 14px;
font-weight: 500;
text-decoration: none;
cursor: pointer;
transition: transform 0.2s ease, background-color 0.2s ease;
}
.kodza-empty__button--quiet:not(#_) {
background: var(--kz-empty-soft);
color: var(--kz-empty-fg);
}
.kodza-empty__button:active:not(#_),
.kodza-empty__chip:active:not(#_) {
transform: scale(0.97);
}
.kodza-empty__chip:not(#_) {
height: 32px;
padding: 0 12px;
border: 0;
border-radius: 999px;
background: var(--kz-empty-soft);
color: var(--kz-empty-fg);
font-family: "Geist Mono", ui-monospace, "SF Mono", Menlo, monospace;
font-size: 13px;
cursor: pointer;
transition: background-color 0.2s ease, color 0.2s ease, transform 0.2s ease;
}
.kodza-empty__chip:hover:not(#_) {
background: var(--kz-empty-ink);
color: var(--kz-empty-on-ink);
}
.kodza-empty__button:focus-visible:not(#_),
.kodza-empty__chip:focus-visible:not(#_) {
outline: 2px solid var(--kz-empty-fg);
outline-offset: 2px;
}
@media (prefers-reduced-motion: reduce) {
.kodza-empty__card svg:not(#_),
.kodza-empty__lens:not(#_) {
transition-duration: 0.01ms !important;
}
}
@media (prefers-reduced-motion: reduce) {
.kodza-empty__button:not(#_),
.kodza-empty__chip:not(#_) {
transition-duration: 0.01ms !important;
}
}
'use client';
// Kodza — Empty State, for React. From https://kodza.dev/components/empty-state/
// 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: empty-state.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 './empty-state.css';
type Variant = "search";
export type EmptyStateProps = Omit<ComponentPropsWithoutRef<'div'>, "children" | "variant" | "vars"> & {
/** Modifier class(es): .kodza-empty--search */
variant?: Variant | Variant[];
/** Settings, as CSS variables on the root */
vars?: {
/** default: transparent */
"--kz-empty-bg"?: string | number;
/** default: #0a0a0a */
"--kz-empty-fg"?: string | number;
/** default: rgba(10, 10, 10, 0.62) */
"--kz-empty-muted"?: string | number;
/** default: rgba(10, 10, 10, 0.18) */
"--kz-empty-line"?: string | number;
/** default: rgba(10, 10, 10, 0.06) */
"--kz-empty-soft"?: string | number;
/** default: #ffffff */
"--kz-empty-card"?: string | number;
/** default: #0a0a0a */
"--kz-empty-ink"?: string | number;
/** default: #ffffff */
"--kz-empty-on-ink"?: string | number;
/** default: cubic-bezier(0.22, 1, 0.36, 1) */
"--kz-empty-ease"?: string | number;
};
className?: string;
};
export function EmptyState({ variant, vars, className, style, ...rest }: EmptyStateProps) {
const ref = useRef<ComponentRef<'div'>>(null);
const classes = ["kodza-empty", ...([] as string[]).concat(variant ?? []).map((v) => `kodza-empty--${v}`), 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, style, rest]);
return (
<div key={key} {...rest} className={classes} style={{ ...vars, ...style } as CSSProperties} ref={ref}>
<div className="kodza-empty__art" aria-hidden="true">
<span className="kodza-empty__card" />
<span className="kodza-empty__card" />
<span className="kodza-empty__card">
<svg viewBox="0 0 16 16">
<path d="M8 4v8M4 8h8" />
</svg>
</span>
</div>
<h3 className="kodza-empty__title">Nothing here yet</h3>
<p className="kodza-empty__text">Your collection is as empty as a Monday standup. Save a component and it lands here.</p>
<div className="kodza-empty__actions">
<a className="kodza-empty__button" href="#components">Browse components</a>
{' '}
<button className="kodza-empty__button kodza-empty__button--quiet" type="button">Import a list</button>
</div>
</div>
);
}
Checked
Robots poked at it before you did. Every component goes through the same checks, and again each time it changes.