Skip to content
txbrendan/ui

Texture

beta

Sets how the parts inside it draw their color, like charts, meters, and buttons: dots, a gradient, or grain, with a soft glow on dark pages.

Preview

dither

Monthly recurring revenue

$48,210

Compiling 214 modules
x

gradient

Monthly recurring revenue

$48,210

Compiling 214 modules
x

grain

Monthly recurring revenue

$48,210

Compiling 214 modules
x

flat

Monthly recurring revenue

$48,210

Compiling 214 modules
x

texture

keep flat

choices

Steps from the Texture

Its own scatter wins

Install

  1. 1. Add the registry to components.json, once per app. Without it, the shadcn CLI looks up @txbrendan in a public list that txbrendan/ui does not control.

    npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
  2. 2. Add the theme, once per app. It replaces your color tokens with ones checked for contrast.

    npx shadcn@latest add @txbrendan/theme
  3. 3. Add the component.

    npx shadcn@latest add @txbrendan/dither

Use it for

  • A dashboard, a status page, or a landing page that wants a texture on its charts and meters.
  • Wrap a card, a section, or the whole app in Texture. Every part inside that has a texture prop draws its color that way. Dither is dots, gradient fades from the full color to a faint wash, and grain is fine speckle.
  • Hover a gradient or grain part to answer the pointer: gradient's full color runs down the part, and grain fills in to solid. With reduced motion, gradient fades in place.
  • Pass texture on one part to change just that part, with no wrapper.
  • Pass texture="flat" on a part, or on an inner Texture, to keep it flat inside a Texture.
  • Dither is the old name for Texture, and it still works.
  • Pass fill, start, line, intro, hover, or idle to set how every Area Chart inside lays out and moves its dots. Each sets a --dither- variable, like --dither-intro, so the same choice can sit on <body> in CSS. Bar Chart reads fill, intro, hover, and idle. Its bars have no line or start.
  • Parts that take texture: Sparkline, Progress, Ring Progress, Button, Avatar, and Slideshow take all four. Bar Chart and Area Chart take flat and dither, and draw flat under gradient or grain.
  • A change of texture fades one into the other where it sits, over --motion-duration-slow. A box that moves --dither itself, like a card that cycles between flat and dots, sets --dither-fade: 0s on itself, so the parts inside follow it with no second fade.
  • A chart of your own. Put DitherBoxMask in its SVG defs with the plot's box, and mask the filled shapes with it. The dots snap to the plot and thin toward its bottom. Pass thin "none" for even dots, thin "dense" for dots solid at the top that stay dense longer, or a box's from with thin "from" to thin from a bar's own top. Dither Motion moves them.

Avoid it for

  • Text people must read. Textures thin out the shapes, so keep body text, labels, and numbers flat.
  • Saying something only with a texture. It is a look, and the part still says its value in text or its name.
  • A dense table or form. The texture adds noise where people need to scan.
  • An animation class of your own on a dithered part, like a pulse while it loads. The dots' intro sets that part's animation, so one of the two is lost. Put yours on a wrapper around the part.

Props

  • texture

    The texture every part inside draws. "flat" turns it off again inside another Texture.

    "flat" | "dither" | "gradient" | "grain" · default "dither"

  • fill

    How the dots in every Area Chart and Bar Chart inside thin toward the bottom: one by one, or in 8 even steps. Its own fill wins.

    "scatter" | "steps"

  • start

    How every Area Chart inside starts: fading in from the axis, or 16 px in from it. Its own start wins.

    "fade" | "gap"

  • line

    How each Area Chart line inside starts: fading in, or breaking into dots. Its own line wins.

    "fade" | "dots"

  • intro

    How the dots in every Area Chart and Bar Chart inside come in. Its own intro wins.

    "fade" | "sweep" | "rain" | "ripple" | "pop" | "fall"

  • hover

    Whether hidden dots fill in near the pointer, in every Area Chart and Bar Chart inside. Its own hover wins.

    "fill" | "none"

  • idle

    What the dots in every Area Chart and Bar Chart inside do at rest. Its own idle wins.

    "none" | "wave" | "twinkle" | "breathe" | "drift"

  • children

    The parts that take the texture.

    ReactNode

  • className

    Extra classes. Texture draws no box of its own, since it is display: contents.

    string

Accessibility

  • Texture changes only how color is drawn. Names, roles, values, and keyboard behavior stay the same.
  • Texture adds no box, so it never changes layout, focus order, or what a screen reader hears.
  • A textured shape keeps a faint wash of its color under the dots, gradient, or grain, so it still reads as one piece.
  • Hover only runs on screens that can hover, so a tap never leaves it stuck. It is a fade, and with reduced motion gradient fades in place instead of running down.
  • The glow shows only on dark pages, through --elevation-glow, which is 0px in light.
  • When a dithered part first shows, its dots come in one at a time, over 40% of --motion-pulse-duration, and only once. A part further down the page waits until it starts to come on screen, then plays at the same pace however fast it was scrolled to. Button and Avatar wait with CSS alone, in browsers that can tie an animation to scrolling, and play on mount in the rest.
  • Button and Avatar follow their nearest scrolling box, and a box with overflow hidden or auto counts as one even when it cannot scroll. Inside such a box, their dots wait for the box to scroll them into view, and play on mount when they already sit in its view, even while the box itself is off screen. A box with overflow-clip still clips but is not a scrolling box, so parts inside it wait for the page.
  • With reduced motion or motion off, the pulse is 0s, so every dot shows at once. A print shows every dot.
  • A change of texture is a fade, so reduced motion keeps it. With motion off it swaps at once.

Examples

A dithered dashboard card

<Texture>
  <BarChart
    title="Deploys to production"
    data={[{ week: "W1", deploys: 52 }, { week: "W2", deploys: 61 }, { week: "W3", deploys: 74 }]}
    config={{ deploys: { label: "Deploys" } }}
    categoryKey="week"
  />
  <Progress value={28} tone="brand" label="Compiling 214 modules" showValue />
</Texture>

Gradient meters

<Texture texture="gradient">
  <RingProgress value={68} tone="brand" aria-label="CPU load" />
  <Progress value={28} tone="brand" label="Compiling 214 modules" showValue />
</Texture>

One part, no wrapper

<Button tone="brand" texture="grain">Save changes</Button>

Keep one part flat

<Texture>
  <RingProgress value={68} tone="brand" aria-label="CPU load" />
  <RingProgress value={40} texture="flat" aria-label="Memory" />
</Texture>