Tooth Grain
Paper tooth grain texture: a single-octave noise tile at overlay blend, so a surface reads as fine paper without clumping into a pattern. Intensity steps or an exact opacity; full-bleed in a relative parent.
pnpm dlx shadcn add @01orbital/tooth-grain// Paper tooth: one octave, so it never clumps into a pattern; 250px tile at
// overlay. Absolutely positioned child — the parent needs `relative`.
import { cn } from "cn"
type Intensity = "fine" | "default" | "rich"
// The intensity steps; `default` is the unscaled value.
const INTENSITY_MULT: Record<Intensity, number> = {
fine: 0.6,
default: 1,
rich: 1.45,
}
const NOISE = `data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='250' height='250' viewBox='0 0 100 100'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='1' seed='2' stitchTiles='stitch' result='n'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E`
function ToothGrain({
intensity = "default",
// Exact opacity. Overrides intensity.
opacity,
className,
style,
...props
}: React.ComponentProps<"div"> & {
intensity?: Intensity
opacity?: number
}) {
return (
<div
aria-hidden="true"
data-slot="tooth-grain"
className={cn(
"pointer-events-none absolute inset-0 mix-blend-overlay",
className
)}
style={{
backgroundImage: `url("${NOISE}")`,
opacity: opacity ?? 0.2 * INTENSITY_MULT[intensity],
...style,
}}
{...props}
/>
)
}
export { ToothGrain }
import { ToothGrain } from "@/components/texture/tooth-grain"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
/** A sized tooth surface with readable studio details. */
export default function ToothDemo() {
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-[#d8c7b6] p-6 wrap-anywhere text-[#352e2b] shadow-sm ring-1 ring-black/10 [--muted-foreground:#605248] sm:p-8">
<ToothGrain intensity="rich" />
<CardHeader className="relative z-20 gap-6 px-0">
<p className="text-sm font-medium">West Drawing Room</p>
<div className="grid gap-1">
<CardTitle className="text-2xl/8 font-medium tracking-tight">
Jules Bennett
</CardTitle>
<CardDescription>Artist & drawing tutor</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>3 West Arcade, Exeter</span>
<a
href="mailto:hello@westdrawing.example"
className="break-all underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-4"
>
hello@westdrawing.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 { ToothGrain } from "@/components/texture/tooth-grain"
import {
Card,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card"
/** A sized tooth surface with readable studio details. */
export default function ToothDemo() {
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-[#d8c7b6] p-6 wrap-anywhere text-[#352e2b] shadow-sm ring-1 ring-black/10 [--muted-foreground:#605248] sm:p-8">
<ToothGrain intensity="rich" />
<CardHeader className="relative z-20 gap-6 px-0">
<p className="text-sm font-medium">West Drawing Room</p>
<div className="grid gap-1">
<CardTitle className="text-2xl/8 font-medium tracking-tight">
Jules Bennett
</CardTitle>
<CardDescription>Artist & drawing tutor</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>3 West Arcade, Exeter</span>
<a
href="mailto:hello@westdrawing.example"
className="break-all underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-offset-4"
>
hello@westdrawing.example
</a>
</CardFooter>
</Card>
</div>
)
}Examples
New
tooth-grain-studio-card
New
tooth-grain-workshop-invitation
New
tooth-grain-seasonal-menu
New
tooth-grain-field-note
New
tooth-grain-edition-cover
New
tooth-grain-order-receipt
New
tooth-grain-collection-label
New
tooth-grain-studio-letter
New
tooth-grain-quote-card
New
tooth-grain-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.