Counter
A compact numeric pill for unread items and pending tasks.
npx shadcn@latest add @velaui/counterpnpm dlx velaui add @velaui/counteryarn dlx shadcn@latest add @velaui/counterbunx --bun shadcn@latest add @velaui/counterveni add counterInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/counterpnpm dlx velaui add @velaui/counteryarn dlx shadcn@latest add @velaui/counterbunx --bun shadcn@latest add @velaui/counterExamples
- Default
- Accent low
- Red high small
- Blue low small
- Green high
- Yellow low overflow
Usage
Props
| Prop | Type | Default |
|---|---|---|
asChild | boolean | false |
emphasis | "high" | "low" | — |
size | "small" | "default" | — |
variant | "default" | "red" | "green" | "blue" | "yellow" | "accent" | — |
Takes these and every prop of CounterProps.
| Prop | Type | Default |
|---|---|---|
emphasis | "high" | "low" | — |
size | "small" | "default" | — |
variant | "default" | "red" | "green" | "blue" | "yellow" | "accent" | — |
Takes these and every prop of CounterProps.
Dependencies
- npm
class-variance-authority@0.7.1radix-ui@1.6.0- Installs with it
- nothing else
- npm
@base-ui/react@1.6.0class-variance-authority@0.7.1- Installs with it
- nothing else
Source
components/ui/counter.tsx
/* eslint-disable react-refresh/only-export-components */
import * as React from "react";
import { Slot } from "radix-ui";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* Counter — a compact numeric pill (unread items, pending tasks).
*
* The design crosses `Appearance` (the colour) with `Emphasis` (solid fill vs
* tinted surface), so the twelve colour pairs live in `compoundVariants`; the
* `variants` blocks carry only what a single axis can decide on its own.
* The design draws no interactive state — this is a display element.
*/
const counterVariants = cva(
"inline-flex shrink-0 flex-col items-center justify-center overflow-hidden rounded-full px-1 py-0.5 text-center whitespace-nowrap tabular-nums",
{
variants: {
variant: {
default: "",
accent: "",
red: "",
blue: "",
green: "",
yellow: "",
},
emphasis: {
high: "",
low: "",
},
size: {
// Height comes from padding + line-height: 2 + 16 + 2 = 20.
default: "min-w-5 text-xs leading-4 font-medium",
// 2 + 12 + 2 = 16.
small: "min-w-4 text-[10px] leading-3 font-semibold",
},
},
compoundVariants: [
{
variant: "default",
emphasis: "high",
className: "bg-bg-foreground text-text-inverse",
},
{
variant: "accent",
emphasis: "high",
className: "bg-accent-default text-base-white",
},
{
variant: "red",
emphasis: "high",
className: "bg-negative-middle text-base-white",
},
{
variant: "blue",
emphasis: "high",
className: "bg-info-middle text-base-white",
},
{
variant: "green",
emphasis: "high",
className: "bg-success-middle text-base-white",
},
{
variant: "yellow",
emphasis: "high",
className: "bg-warning-middle text-base-white",
},
{
variant: "default",
emphasis: "low",
className: "bg-bg-surface-1 text-text-strong",
},
{
variant: "accent",
emphasis: "low",
className: "bg-teal-50 text-neutral-950",
},
{
variant: "red",
emphasis: "low",
className: "bg-negative text-negative-middle",
},
{
variant: "blue",
emphasis: "low",
className: "bg-info text-info-middle",
},
{
variant: "green",
emphasis: "low",
className: "bg-success text-success-middle",
},
{
variant: "yellow",
emphasis: "low",
className: "bg-warning text-warning-middle",
},
],
defaultVariants: {
variant: "default",
emphasis: "high",
size: "default",
},
},
);
export type CounterProps = React.ComponentProps<"span"> &
VariantProps<typeof counterVariants> & {
asChild?: boolean;
};
function Counter({
className,
variant,
emphasis,
size,
asChild = false,
...props
}: CounterProps) {
const Comp = asChild ? Slot.Root : "span";
return (
<Comp
data-slot="counter"
data-variant={variant}
data-emphasis={emphasis}
data-size={size}
className={cn(counterVariants({ variant, emphasis, size }), className)}
{...props}
/>
);
}
export { Counter, counterVariants };components/ui/counter.tsx
/* eslint-disable react-refresh/only-export-components */
import { mergeProps } from "@base-ui/react/merge-props";
import { useRender } from "@base-ui/react/use-render";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
/**
* Counter — a compact numeric pill (unread items, pending tasks).
*
* The design crosses `Appearance` (the colour) with `Emphasis` (solid fill vs
* tinted surface), so the twelve colour pairs live in `compoundVariants`; the
* `variants` blocks carry only what a single axis can decide on its own.
* The design draws no interactive state — this is a display element.
*/
const counterVariants = cva(
"inline-flex shrink-0 flex-col items-center justify-center overflow-hidden rounded-full px-1 py-0.5 text-center whitespace-nowrap tabular-nums",
{
variants: {
variant: {
default: "",
accent: "",
red: "",
blue: "",
green: "",
yellow: "",
},
emphasis: {
high: "",
low: "",
},
size: {
// Height comes from padding + line-height: 2 + 16 + 2 = 20.
default: "min-w-5 text-xs leading-4 font-medium",
// 2 + 12 + 2 = 16.
small: "min-w-4 text-[10px] leading-3 font-semibold",
},
},
compoundVariants: [
{
variant: "default",
emphasis: "high",
className: "bg-bg-foreground text-text-inverse",
},
{
variant: "accent",
emphasis: "high",
className: "bg-accent-default text-base-white",
},
{
variant: "red",
emphasis: "high",
className: "bg-negative-middle text-base-white",
},
{
variant: "blue",
emphasis: "high",
className: "bg-info-middle text-base-white",
},
{
variant: "green",
emphasis: "high",
className: "bg-success-middle text-base-white",
},
{
variant: "yellow",
emphasis: "high",
className: "bg-warning-middle text-base-white",
},
{
variant: "default",
emphasis: "low",
className: "bg-bg-surface-1 text-text-strong",
},
{
variant: "accent",
emphasis: "low",
className: "bg-teal-50 text-neutral-950",
},
{
variant: "red",
emphasis: "low",
className: "bg-negative text-negative-middle",
},
{
variant: "blue",
emphasis: "low",
className: "bg-info text-info-middle",
},
{
variant: "green",
emphasis: "low",
className: "bg-success text-success-middle",
},
{
variant: "yellow",
emphasis: "low",
className: "bg-warning text-warning-middle",
},
],
defaultVariants: {
variant: "default",
emphasis: "high",
size: "default",
},
},
);
export type CounterProps = useRender.ComponentProps<"span"> &
VariantProps<typeof counterVariants>;
function Counter({
className,
variant,
emphasis,
size,
render,
...props
}: CounterProps) {
return useRender({
defaultTagName: "span",
props: mergeProps<"span">(
{
className: cn(counterVariants({ variant, emphasis, size }), className),
},
props,
),
render,
state: {
slot: "counter",
variant,
emphasis,
size,
},
});
}
export { Counter, counterVariants };