Sketch Provider

Sketch Provider

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.

Installation
Adding any component pulls sketch-provider in as a dependency, but you can install it on its own.
npx shadcn@latest add https://sketchcn.chuwii.com/r/sketch-provider.json
Wrap your app once, at the root
import { 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.

SketchProvider
Shares the seed with every sketch component below it. Every other drawing option comes from the --sketch-* CSS variables.
NameTypeDefaultCSS variableDescription
childrenReactNodeThe tree that can read sketch settings.
seednumber20260828--sketch-seedBase seed for the wobble. Every hook mixes it with its own id, so one seed change reshuffles the whole page deterministically.
Draw the whole page rougher
<SketchProvider seed={1234}>
  <div className="[--sketch-roughness:2] [--sketch-bowing:2]">{children}</div>
</SketchProvider>
Option precedence, lowest to highest

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.

useSketchOutline
Returns a ref and a style for an absolutely positioned svg that traces the parent element.
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.

NameTypeDefaultCSS variableDescription
shape"rectangle" | "underline""rectangle"Rectangle follows the element border radius; underline draws a single stroke along the bottom edge.
idstringuseId()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.
opacitynumberApplied to the returned svg style. Style the svg directly in CSS instead when you want a hover or state transition.
...roughOptionsPartial<Options>--sketch-*Any RoughJS option except seed: roughness, bowing, stroke, strokeWidth, strokeLineDash, fill, fillStyle, and friends.
Live
Default
Dashed
Rougher
Underline
useSketchBg
useSketchOutline drawn from the --sketch-bg-* variables, giving a hatched fill and no stroke. Anything it does not define falls back to the shared --sketch-* value.
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>
  );
}
NameTypeDefaultCSS variableDescription
fillcolorcurrentColor--sketch-bg-fillFill colour of the hachure strokes.
fillStylestringhachure--sketch-bg-fill-styleRoughJS fill style used for the shading.
fillWeightnumber0.4--sketch-bg-fill-weightThickness of each hachure stroke.
hachureGapnumber4--sketch-bg-hachure-gapDistance between hachure strokes.
strokecolortransparent--sketch-bg-strokeNo outline is drawn, only the fill.
opacitynumber0.5--sketch-bg-opacityKeeps the shading behind the content readable.
Live
Hachure
Cross hatch
Zigzag
CSS variables
sketch.css ships defaults on :root, and any element can override them for its own outline.
NameTypeDefaultDescription
--sketch-seedquoted 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-roughnessnumber1.1How far strokes stray from the ideal path.
--sketch-bowingnumber1.4How much straight lines bend.
--sketch-strokecolorcurrentColorStroke colour of the outline.
--sketch-stroke-widthnumber1.6Stroke width in pixels.
--sketch-disable-multi-stroke"true" | "false"trueDraw one pass instead of the doubled pencil pass.
--sketch-preserve-vertices"true" | "false"trueKeep corners anchored so shapes stay aligned with the layout box.
--sketch-fillcolorFill colour, also used by the CSS fill transition on drawn paths.
--sketch-fill-stylestringsolidRoughJS fill style, such as solid, hachure, or zigzag.
--sketch-fill-weightnumberThickness of each fill stroke for hatched fill styles.
--sketch-hachure-gapnumberDistance between hachure strokes.
--sketch-hachure-anglenumberAngle of the hachure lines in degrees.
--sketch-dashstringnoneCSS stroke-dasharray on the drawn outline, separate from the RoughJS dashed fill.
--sketch-dash-animationstringnoneCSS animation shorthand for the outline, used by the boil and alternate keyframes.
--sketch-fill-opacitynumber1CSS fill-opacity on the drawn path, transitioned over 150ms.
Two values, side by side
--sketch-seed
7
99
--sketch-roughness
0.4
3
--sketch-bowing
0
6
--sketch-stroke
var(--primary)
var(--destructive)
--sketch-stroke-width
1
4
--sketch-disable-multi-stroke
true
false
--sketch-preserve-vertices
true
false
--sketch-fill
var(--primary)
var(--destructive)
--sketch-fill-style
solid
hachure
--sketch-fill-weight
0.5
2.5
--sketch-hachure-gap
3
10
--sketch-hachure-angle
0
90
--sketch-dash
6 4
2 10
--sketch-dash-animation
sketch-dash-boil 1s steps(2) infinite
sketch-dash-alternate 1s steps(2) infinite
--sketch-fill-opacity
0.2
1
Override on a subtree
<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.