Every Sketchcn component draws its outline with RoughJS. SketchProvider holds the drawing settings, and the useSketchOutline and useSketchBg hooks turn any element into a hand-drawn one.
npx shadcn@latest add https://sketchcn.chuwii.com/r/sketch-provider.jsonimport { SketchProvider } from "@/components/ui/sketch-provider";
export function App({ children }: { children: React.ReactNode }) {
return <SketchProvider>{children}</SketchProvider>;
}The hooks throw when there is no provider above them, so a missing wrapper fails loudly instead of rendering unstyled boxes.
| Name | Type | Default | CSS variable | Description |
|---|---|---|---|---|
| children | ReactNode | — | — | The tree that can read sketch settings. |
| seed | number | 20260828 | --sketch-seed | Base seed for the wobble. Every hook mixes it with its own id, so one seed change reshuffles the whole page deterministically. |
<SketchProvider seed={1234}>
<div className="[--sketch-roughness:2] [--sketch-bowing:2]">{children}</div>
</SketchProvider>CSS variables read from the element, then the options passed to the hook. The seed is the exception. It comes from --sketch-seed when the element sets one, otherwise from the provider, and is always mixed with the hook id.
import { useSketchOutline } from "@/components/ui/sketch-provider";
export function Panel({ children }: { children: React.ReactNode }) {
const sketchOutline = useSketchOutline();
return (
<div className="relative isolate rounded-lg px-4 py-3">
{children}
<svg
aria-hidden="true"
data-sketch-outline
className="-z-10"
ref={sketchOutline.ref}
style={sketchOutline.style}
/>
</div>
);
}The parent needs position: relative and isolate so the svg can sit behind the content. The outline is measured from the padding box and redrawn whenever the element resizes or its class or style attribute changes.
| Name | Type | Default | CSS variable | Description |
|---|---|---|---|---|
| shape | "rectangle" | "underline" | "rectangle" | — | Rectangle follows the element border radius; underline draws a single stroke along the bottom edge. |
| id | string | useId() | — | Seeds the wobble. Because useId() shifts with tree position, pass a stable id when the geometry must not change, such as shared shapes or visual snapshots. |
| opacity | number | — | — | Applied to the returned svg style. Style the svg directly in CSS instead when you want a hover or state transition. |
| ...roughOptions | Partial<Options> | — | --sketch-* | Any RoughJS option except seed: roughness, bowing, stroke, strokeWidth, strokeLineDash, fill, fillStyle, and friends. |
import { useSketchBg } from "@/components/ui/sketch-provider";
export function Highlight({ children }: { children: React.ReactNode }) {
const sketchBg = useSketchBg({ hachureGap: 6 });
return (
<div className="relative isolate rounded-lg px-4 py-3 text-primary">
{children}
<svg
aria-hidden="true"
data-sketch-bg
className="-z-10"
ref={sketchBg.ref}
style={sketchBg.style}
/>
</div>
);
}| Name | Type | Default | CSS variable | Description |
|---|---|---|---|---|
| fill | color | currentColor | --sketch-bg-fill | Fill colour of the hachure strokes. |
| fillStyle | string | hachure | --sketch-bg-fill-style | RoughJS fill style used for the shading. |
| fillWeight | number | 0.4 | --sketch-bg-fill-weight | Thickness of each hachure stroke. |
| hachureGap | number | 4 | --sketch-bg-hachure-gap | Distance between hachure strokes. |
| stroke | color | transparent | --sketch-bg-stroke | No outline is drawn, only the fill. |
| opacity | number | 0.5 | --sketch-bg-opacity | Keeps the shading behind the content readable. |
| Name | Type | Default | Description |
|---|---|---|---|
| --sketch-seed | quoted number | "20260828" | Base seed for the wobble, mixed with each instance id, overriding the SketchProvider seed for the subtree. Quote it, because CSS minifiers round bare numbers to six significant digits. |
| --sketch-roughness | number | 1.1 | How far strokes stray from the ideal path. |
| --sketch-bowing | number | 1.4 | How much straight lines bend. |
| --sketch-stroke | color | currentColor | Stroke colour of the outline. |
| --sketch-stroke-width | number | 1.6 | Stroke width in pixels. |
| --sketch-disable-multi-stroke | "true" | "false" | true | Draw one pass instead of the doubled pencil pass. |
| --sketch-preserve-vertices | "true" | "false" | true | Keep corners anchored so shapes stay aligned with the layout box. |
| --sketch-fill | color | — | Fill colour, also used by the CSS fill transition on drawn paths. |
| --sketch-fill-style | string | solid | RoughJS fill style, such as solid, hachure, or zigzag. |
| --sketch-fill-weight | number | — | Thickness of each fill stroke for hatched fill styles. |
| --sketch-hachure-gap | number | — | Distance between hachure strokes. |
| --sketch-hachure-angle | number | — | Angle of the hachure lines in degrees. |
| --sketch-dash | string | none | CSS stroke-dasharray on the drawn outline, separate from the RoughJS dashed fill. |
| --sketch-dash-animation | string | none | CSS animation shorthand for the outline, used by the boil and alternate keyframes. |
| --sketch-fill-opacity | number | 1 | CSS fill-opacity on the drawn path, transitioned over 150ms. |
<div className="[--sketch-roughness:2.6] [--sketch-bowing:2.4]">
<Button>Rougher button</Button>
</div>Variables are read from the drawn element itself, so they cascade like any other CSS custom property and work with Tailwind arbitrary properties, variants, and dark mode.