Commerce
Stock indicator
A small inline stock status: in stock, low, out or on backorder, with a glyph and words for each.
- Built on
<time>Intl.DateTimeFormat- Client JavaScript
- None
- Works without React
- Yes, as HTML and CSS
- Source, gzipped
- 1.2 kB
- Built from
- Icon
In stockOnly 3 leftBack in stock Ships from
Try its options
Loading its options.
Code
import { StockIndicator } from "@/components/equip/stock-indicator";
export default function Demo() {
return (
<div style={{ display: "grid", gap: 6, justifyItems: "start" }}>
<StockIndicator status="in-stock" />
<StockIndicator status="low" count={3} />
<StockIndicator status="out" date="2026-12-04" />
<StockIndicator status="backorder" date="2027-01-12" />
</div>
);
}Your versionchange its options in Look, This part
HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:6px;justify-items:start">
<span class="eq-stock-indicator" data-status="in-stock" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M8 12.5l2.75 2.75L16 9.5" />
</svg>
<span class="eq-stock-indicator-label">In stock</span>
</span>
<span class="eq-stock-indicator" data-status="low" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 4 21 19.5H3z" />
<path d="M12 9.5v3.5M12 16.75v.01" />
</svg>
<span class="eq-stock-indicator-label">Only 3 left</span>
</span>
<span class="eq-stock-indicator" data-status="out" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M9.25 9.25l5.5 5.5M14.75 9.25l-5.5 5.5" />
</svg>
<span class="eq-stock-indicator-label">Back in stock <time dateTime="2026-12-04">December 4</time></span>
</span>
<span class="eq-stock-indicator" data-status="backorder" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 7v5l3.5 2.5" />
</svg>
<span class="eq-stock-indicator-label">Ships from <time dateTime="2027-01-12">January 12</time></span>
</span>
</div>Examples
On a product page
Sits under the price and beside the quantity, where the decision to buy is made.
Ceramic pour-over set
Quantity: 1Only 3 left
Order by 2 pm for next-day deliveryCode
import { StockIndicator } from "@/components/equip/stock-indicator";
export default function Example() {
return (
<div style={{ display: "grid", gap: 8, maxWidth: 360 }}>
<div style={{ fontWeight: 650 }}>Ceramic pour-over set</div>
<div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 12 }}>
<span style={{ color: "var(--eq-ink-2)", fontSize: 13 }}>Quantity: 1</span>
<StockIndicator status="low" count={3} />
</div>
<StockIndicator status="in-stock" size="s">
Order by 2 pm for next-day delivery
</StockIndicator>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:8px;max-width:360px">
<div style="font-weight:650">Ceramic pour-over set</div>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:12px">
<span style="color:var(--eq-ink-2);font-size:13px">Quantity: 1</span>
<span class="eq-stock-indicator" data-status="low" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 4 21 19.5H3z" />
<path d="M12 9.5v3.5M12 16.75v.01" />
</svg>
<span class="eq-stock-indicator-label">Only 3 left</span>
</span>
</div>
<span class="eq-stock-indicator" data-status="in-stock" data-size="s">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M8 12.5l2.75 2.75L16 9.5" />
</svg>
<span class="eq-stock-indicator-label">Order by 2 pm for next-day delivery</span>
</span>
</div>Own wording and locale
children replaces the text and keeps the glyph; locale formats the date in a backorder or restock line.
Ready to collect from Leeds todayMade to order, ships in 3 weeksBack in stock Ausverkauft
Code
import { StockIndicator } from "@/components/equip/stock-indicator";
export default function Example() {
return (
<div style={{ display: "grid", gap: 8, justifyItems: "start" }}>
<StockIndicator status="in-stock">Ready to collect from Leeds today</StockIndicator>
<StockIndicator status="backorder">Made to order, ships in 3 weeks</StockIndicator>
<StockIndicator status="out" date="2026-12-04" locale="en-GB" />
<StockIndicator status="out" lang="de">
Ausverkauft
</StockIndicator>
</div>
);
}HTMLworks in any framework, with the part's CSS
<div style="display:grid;gap:8px;justify-items:start">
<span class="eq-stock-indicator" data-status="in-stock" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M8 12.5l2.75 2.75L16 9.5" />
</svg>
<span class="eq-stock-indicator-label">Ready to collect from Leeds today</span>
</span>
<span class="eq-stock-indicator" data-status="backorder" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 7v5l3.5 2.5" />
</svg>
<span class="eq-stock-indicator-label">Made to order, ships in 3 weeks</span>
</span>
<span class="eq-stock-indicator" data-status="out" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M9.25 9.25l5.5 5.5M14.75 9.25l-5.5 5.5" />
</svg>
<span class="eq-stock-indicator-label">Back in stock <time dateTime="2026-12-04">4 December</time></span>
</span>
<span lang="de" class="eq-stock-indicator" data-status="out" data-size="m">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="16" style="--_size:16px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M9.25 9.25l5.5 5.5M14.75 9.25l-5.5 5.5" />
</svg>
<span class="eq-stock-indicator-label">Ausverkauft</span>
</span>
</div>Stock by size
In a list of variants the small size keeps every row to one line.
- XSOut of stock
- SOnly 2 left
- MIn stock
- LIn stock
- XLShips from
Code
import { StockIndicator } from "@/components/equip/stock-indicator";
const sizes = [
{ size: "XS", status: "out" },
{ size: "S", status: "low", count: 2 },
{ size: "M", status: "in-stock" },
{ size: "L", status: "in-stock" },
{ size: "XL", status: "backorder", date: "2026-12-18" },
] as const;
export default function Example() {
return (
<ul style={{ display: "grid", gap: 6, margin: 0, padding: 0, listStyle: "none", maxWidth: 280 }}>
{sizes.map((row) => (
<li
key={row.size}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: 12,
paddingBottom: 6,
borderBottom: "1px solid var(--eq-line)",
fontSize: 13,
}}
>
<span style={{ fontWeight: 650 }}>{row.size}</span>
<StockIndicator
size="s"
status={row.status}
count={"count" in row ? row.count : undefined}
date={"date" in row ? row.date : undefined}
/>
</li>
))}
</ul>
);
}HTMLworks in any framework, with the part's CSS
<ul style="display:grid;gap:6px;margin:0;padding:0;list-style:none;max-width:280px">
<li style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding-bottom:6px;border-bottom:1px solid var(--eq-line);font-size:13px">
<span style="font-weight:650">XS</span>
<span class="eq-stock-indicator" data-status="out" data-size="s">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M9.25 9.25l5.5 5.5M14.75 9.25l-5.5 5.5" />
</svg>
<span class="eq-stock-indicator-label">Out of stock</span>
</span>
</li>
<li style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding-bottom:6px;border-bottom:1px solid var(--eq-line);font-size:13px">
<span style="font-weight:650">S</span>
<span class="eq-stock-indicator" data-status="low" data-size="s">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 4 21 19.5H3z" />
<path d="M12 9.5v3.5M12 16.75v.01" />
</svg>
<span class="eq-stock-indicator-label">Only 2 left</span>
</span>
</li>
<li style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding-bottom:6px;border-bottom:1px solid var(--eq-line);font-size:13px">
<span style="font-weight:650">M</span>
<span class="eq-stock-indicator" data-status="in-stock" data-size="s">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M8 12.5l2.75 2.75L16 9.5" />
</svg>
<span class="eq-stock-indicator-label">In stock</span>
</span>
</li>
<li style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding-bottom:6px;border-bottom:1px solid var(--eq-line);font-size:13px">
<span style="font-weight:650">L</span>
<span class="eq-stock-indicator" data-status="in-stock" data-size="s">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M8 12.5l2.75 2.75L16 9.5" />
</svg>
<span class="eq-stock-indicator-label">In stock</span>
</span>
</li>
<li style="display:flex;justify-content:space-between;align-items:center;gap:12px;padding-bottom:6px;border-bottom:1px solid var(--eq-line);font-size:13px">
<span style="font-weight:650">XL</span>
<span class="eq-stock-indicator" data-status="backorder" data-size="s">
<svg aria-hidden="true" focusable="false" class="eq-icon eq-stock-indicator-glyph" data-size="14" style="--_size:14px" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" />
<path d="M12 7v5l3.5 2.5" />
</svg>
<span class="eq-stock-indicator-label">Ships from <time dateTime="2026-12-18">December 18</time></span>
</span>
</li>
</ul>Props
| Prop | Type | Default | What it does |
|---|---|---|---|
status | "in-stock" | "low" | "out" | "backorder" | none | Which state to show. Sets the glyph, its colour and the default wording. |
count | number | none | Units left. Low stock then reads "Only 3 left". |
date | string | none | ISO date. Out of stock reads "Back in stock" and backorder "Ships from" this date, in a time element. |
locale | string | "en-US" | Formats the date, in UTC so server and browser agree. |
size | "s" | "m" | "m" | s for dense lists of variants. |
children | ReactNode | none | Replaces the default wording. The glyph for the status stays. |
...rest | ComponentProps<"span"> | none | Every native span attribute, including lang and id. |
Anatomy
| root | .eq-stock-indicator | An inline span; data-status picks the tone. |
|---|---|---|
| glyph | .eq-stock-indicator-glyph | A tick, a warning sign, a crossed circle or a clock, decorative. |
| label | .eq-stock-indicator-label |
Notes
- Each status has its own glyph shape and its own words, so colour is never the only signal.
- The glyph is hidden from assistive tech; the text carries the meaning.
- Static text, not a live region: when stock changes after a choice of variant, announce it from the place that changed it.
- The default wording is English. For another language pass children and set lang on the root.
- Renders on the server with no client JavaScript.
Source
Two files, and they are the whole part. Open either to read it, copy it straight from its bar, or add the part to your loadout and install it with everything else you picked.
components/equip/stock-indicator.tsx62 lines
import type { ComponentProps, ReactNode } from "react";
import { Icon } from "./icon";
import "./stock-indicator.css";
export type StockStatus = "in-stock" | "low" | "out" | "backorder";
export type StockIndicatorProps = ComponentProps<"span"> & {
status: StockStatus;
/** Units left. Shown for low stock as "Only 3 left". */
count?: number;
/** ISO date. Out of stock reads "Back in stock" and backorder "Ships from" this date. */
date?: string;
/** Formats the date. The default wording is English; pass children to reword it. */
locale?: string;
size?: "s" | "m";
/** Replaces the default wording. The glyph for the status stays. */
children?: ReactNode;
};
const glyph = {
"in-stock": "check-circle",
low: "warning",
out: "close-circle",
backorder: "clock",
} as const;
export function StockIndicator({
status,
count,
date,
locale = "en-US",
size = "m",
className,
children,
...rest
}: StockIndicatorProps) {
const when =
date === undefined ? null : (
<time dateTime={date}>
{new Intl.DateTimeFormat(locale, { day: "numeric", month: "long", timeZone: "UTC" }).format(new Date(date))}
</time>
);
let text: ReactNode;
if (children != null) text = children;
else if (status === "in-stock") text = "In stock";
else if (status === "low") text = count === undefined ? "Low stock" : `Only ${count} left`;
else if (status === "out") text = when ? <>Back in stock {when}</> : "Out of stock";
else text = when ? <>Ships from {when}</> : "On backorder";
return (
<span
{...rest}
className={["eq-stock-indicator", className].filter(Boolean).join(" ")}
data-status={status}
data-size={size}
>
<Icon name={glyph[status]} size={size === "s" ? 14 : 16} className="eq-stock-indicator-glyph" />
<span className="eq-stock-indicator-label">{text}</span>
</span>
);
}components/equip/stock-indicator.css43 lines
@layer equip {
.eq-stock-indicator {
--_tone: var(--eq-ok);
display: inline-flex;
align-items: center;
gap: var(--eq-space-1);
color: var(--eq-ink);
font: var(--eq-weight) var(--eq-text-s) / 1.3 var(--eq-font);
font-variant-numeric: tabular-nums;
vertical-align: middle;
}
.eq-stock-indicator[data-size="s"] {
font-size: var(--eq-text-xs);
}
.eq-stock-indicator[data-status="low"] {
--_tone: var(--eq-warn);
}
.eq-stock-indicator[data-status="out"] {
--_tone: var(--eq-danger);
}
.eq-stock-indicator[data-status="backorder"] {
--_tone: var(--eq-ink-2);
}
/* Each status has its own glyph shape as well as its colour, so neither carries it alone. */
.eq-stock-indicator-glyph {
color: var(--_tone);
}
.eq-stock-indicator[data-status="low"] .eq-stock-indicator-label,
.eq-stock-indicator[data-status="out"] .eq-stock-indicator-label {
font-weight: var(--eq-weight-strong);
}
.eq-stock-indicator-label time {
white-space: nowrap;
}
}