Stipple
Halftone stipple texture of radial dots, in a default or a dense pitch. White by default, recolored for light grounds; intensity scales dot opacity. A print-style overlay.
pnpm dlx shadcn add @01orbital/stippleimport { cn } from "cn"
type Intensity = "fine" | "default" | "rich"
// One scale for every texture; `default` is the unscaled value.
const INTENSITY_MULT: Record<Intensity, number> = {
fine: 0.6,
default: 1,
rich: 1.45,
}
const alpha = (base: number, mult: number) => (base * mult).toFixed(4)
// Full-bleed art layer — drop inside a `relative` parent; sits behind content.
// Halftone dots — `default` 6px/1px, `dense` 3px/0.5px; `intensity` 0.6x–1.45x.
type StippleVariant = "default" | "dense"
const VARIANT_CONFIG: Record<
StippleVariant,
{ size: string; dotSize: string; baseOpacity: number }
> = {
default: { size: "6px 6px", dotSize: "1px", baseOpacity: 0.04 },
dense: { size: "3px 3px", dotSize: "0.5px", baseOpacity: 0.03 },
}
function Stipple({
variant = "default",
intensity = "default",
className,
style,
...props
}: React.ComponentProps<"div"> & {
variant?: StippleVariant
intensity?: Intensity
}) {
const cfg = VARIANT_CONFIG[variant]
const opacity = (cfg.baseOpacity * INTENSITY_MULT[intensity]).toFixed(4)
return (
// One div, so a caller's `style` lands on the element that paints.
<div
className={cn("pointer-events-none absolute inset-0", className)}
aria-hidden="true"
data-slot="stipple"
style={{
backgroundImage: `radial-gradient(in srgb, oklch(from var(--stipple-color, oklch(1 0 0)) l c h / ${opacity}) ${cfg.dotSize}, transparent ${cfg.dotSize})`,
backgroundSize: cfg.size,
...style,
}}
{...props}
/>
)
}
export { Stipple }
import { Stipple } from "@/components/texture/stipple"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
/** A sized stipple surface with readable studio details. */
export default function StippleDemo() {
return (
<div className="flex min-h-full w-full flex-1 items-center justify-center p-4 sm:p-8">
<Card className="relative isolate w-full max-w-sm gap-6 overflow-hidden rounded-xl bg-[#f1d677] p-6 wrap-anywhere text-[#313c3e] shadow-sm ring-1 ring-black/10 [--muted-foreground:#414d43] sm:p-8">
<Stipple intensity="rich" className="[--stipple-color:#313c3e]" />
<CardHeader className="relative z-20 gap-6 px-0">
<p className="text-sm font-medium">Good Measure</p>
<div className="grid gap-1">
<CardTitle className="text-2xl/8 font-medium tracking-tight">
Owen Clarke
</CardTitle>
<CardDescription>Graphic designer</CardDescription>
</div>
</CardHeader>
<CardFooter className="relative z-20 flex-wrap justify-between gap-3 border-t border-current/20 px-0 pt-5 text-xs/5">
<span>14 Station Street, Margate</span>
<a
href="mailto:hello@goodmeasure.example"
className="break-all underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-4"
>
hello@goodmeasure.example
</a>
</CardFooter>
</Card>
</div>
)
}
The example also uses card. Install it too:
pnpm dlx shadcn add @01orbital/cardnpx shadcn add @01orbital/cardyarn shadcn add @01orbital/cardbunx --bun shadcn add @01orbital/cardUsage
import { Stipple } from "@/components/texture/stipple"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
/** A sized stipple surface with readable studio details. */
export default function StippleDemo() {
return (
<div className="flex min-h-full w-full flex-1 items-center justify-center p-4 sm:p-8">
<Card className="relative isolate w-full max-w-sm gap-6 overflow-hidden rounded-xl bg-[#f1d677] p-6 wrap-anywhere text-[#313c3e] shadow-sm ring-1 ring-black/10 [--muted-foreground:#414d43] sm:p-8">
<Stipple intensity="rich" className="[--stipple-color:#313c3e]" />
<CardHeader className="relative z-20 gap-6 px-0">
<p className="text-sm font-medium">Good Measure</p>
<div className="grid gap-1">
<CardTitle className="text-2xl/8 font-medium tracking-tight">
Owen Clarke
</CardTitle>
<CardDescription>Graphic designer</CardDescription>
</div>
</CardHeader>
<CardFooter className="relative z-20 flex-wrap justify-between gap-3 border-t border-current/20 px-0 pt-5 text-xs/5">
<span>14 Station Street, Margate</span>
<a
href="mailto:hello@goodmeasure.example"
className="break-all underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-4"
>
hello@goodmeasure.example
</a>
</CardFooter>
</Card>
</div>
)
}Examples
New
stipple-studio-card
New
stipple-workshop-invitation
New
stipple-seasonal-menu
New
stipple-field-note
New
stipple-edition-cover
New
stipple-order-receipt
New
stipple-collection-label
New
stipple-studio-letter
New
stipple-quote-card
New
stipple-day-programme
API reference
Prop
Type
It also accepts the HTML attributes of the element it renders.
Dependencies
npm: cn. The CLI installs them for you.
No other registry pieces.