Skip to main content
xriptxr

Nine Patch

Info:Media
0

Artwork sliced into a frame that scales without smearing its corners.

9-patch frame border scalable svg raster skin surface tint slice

Live demo

live · @xtyle/astro

NinePatch

A shape no border can describe

One 96×96 drawing: a plaque with a riveted boss at each corner and a hole punched down every edge. The bosses bulge out past the edge, the rivets are holes you see straight through, and the corners are cut on the diagonal. No combination of border and border-radius gets there. Scale the whole drawing to fill a wider box and the rivets go oval; cut it into nine and the corners keep the size they were drawn at, so only the straight runs between them stretch.

the drawing, at 96×96

scaled to fit — the rivets go oval

sliced at 32 — every rivet still round

A panel you can put things in

A second drawing, torn rather than riveted. With fill the middle is painted too, so the artwork is a surface rather than a frame around a hole, and inset holds the content clear of the ragged edge. Two scraps off the same sheet, each the size its content needed.

A note torn from something bigger. The paper grew to fit this paragraph, and not one tear along its edge got longer to do it.

A shorter scrap, same sheet.

Where the nine regions go

slice makes four cuts in the artwork. The corners never change size, the four edges cover their own run, and the centre takes whatever is left. Both plaques below are the same drawing at the same slice, washed by region — the corner blocks are identical, and only the edges and the middle grew to fill the difference.

10rem wide

the rest of the row, same corners

Tinted from a token

tint uses the artwork as a mask over a colour rather than painting it, so one monochrome drawing wears whatever the algorithm derived. Same torn scrap in all four, and it will follow a theme built long after it was drawn.

--accent

--accent-2

--success

--danger

How the edges cover their run

A third drawing, scalloped. Between the corners sits one tile holding two scallops, and repeat decides what happens to it when the run is longer than the tile. This is the dial that keeps a motif from being pulled out of shape.

stretch — two scallops pulled the length of the run

repeat — scallops at their drawn size, cut where they run out

round — scaled a hair so a whole number fits

space — whole scallops, the slack spread between them

Overriding one region

The nine regions stay addressable. pieces names artwork for any of them and draws it over the sliced base, so a corner can take a crest or the top edge a run of bunting without cutting a new sheet. Anything unnamed is still the base showing through, and tints gives one region its own token.

the base, untouched

two corners given a crest, in their own token

bunting hung across the top edge

Nine Patch cuts a piece of artwork into nine regions: four corners that never scale, four edges that stretch or repeat along their own axis, and a centre that fills. It is the escape hatch for chrome a token cannot describe — a carved frame, a torn edge, a printed border, a game panel — rendered at any size without the corners smearing.

src takes a URL, a data: URI, or raw SVG markup, which it encodes itself; artwork can be a file on disk, a drawing inlined in the page, or something generated at runtime, and vector and raster are equally at home. slice says where the four cuts fall, repeat picks how the edges cover their run (stretch, repeat, round, space), fill paints the middle patch, and inset holds content clear of the frame. tint is the part that belongs to xtyle: instead of painting the artwork it uses it as a mask over a colour, so a single monochrome patch takes var(--accent) — or any token — and follows the theme wherever it lands. Shape from the art, colour from the algorithm, which makes arbitrary surfaces themeable rather than fixed. And the nine regions stay individually addressable: pieces names artwork for any of them and draws it over the sliced base, so one corner can be swapped for a gem or the top edge for a banner without redrawing the sheet. A region left unnamed is simply the base showing through, an opaque override reads as a replacement, and a transparent one as a decoration laid on top; tints can give a single segment its own token.

When to use

How this component composes with the rest of the set.

Wrap a <xtyle-card> or a <xtyle-panel> to give a surface artwork a token cannot describe.
Point tint at var(--accent) and one monochrome drawing serves every theme, including ones built after the artwork was.
Use repeat="round" for artwork with a motif that must not be cut mid-pattern; stretch is right for plain gradients and rules.
Hand src a generated SVG string when the frame itself should derive — the encoding is done for you.
Override one region with pieces to hang a crest on a corner or a banner across the top, without cutting a new sheet.

Props

10 props, straight from the manifest.

PropTypeDefaultBindingsDescription
src string
html svelte astro
The artwork: a URL, a `data:` URI, or raw SVG markup, which is encoded for you.
slice string 33.333%
html svelte astro
Where the four cuts fall, in the artwork's own pixels or as percentages.
width string auto
html svelte astro
How thick the drawn frame is. `auto` takes the slice's own size.
repeat "stretch" | "repeat" | "round" | "space"
stretch repeat round space
stretch
html svelte astro
How each edge covers its run. `round` scales the tile to fit a whole number of repeats.
fill boolean false
html svelte astro
Paint the middle patch too, rather than leaving the centre to the surface beneath.
tint string
html svelte astro
A colour or token to tint the artwork with. The artwork becomes a mask, so one monochrome patch can wear any theme.
pieces Partial<Record<NinePatchRegion, string>>
html svelte astro
Artwork for individual regions — `top-left`, `top`, `top-right`, `left`, `center`, `right`, `bottom-left`, `bottom`, `bottom-right` — drawn over the sliced base. Each takes the same three source forms as `src`. Named regions become their own addressable nodes; the rest stay the base.
tints Partial<Record<NinePatchRegion, string>>
html svelte astro
A per-region tint, overriding the patch's own. Lets one segment wear a different token from the frame it sits on.
inset string
html svelte astro
Padding that holds the slotted content clear of the frame.
outset string 0
html svelte astro
How far the frame extends beyond the element's box.

Appearance

States

tinted

.xtyle-nine-patch__frame--tinted

The artwork is masking a colour rather than being painted directly.

Anatomy

The named parts that make up the component, with their selectors.

frame

.xtyle-nine-patch__frame

The sliced artwork, drawn as its own layer so a tint can mask it without masking the content.

piece

.xtyle-nine-patch__piece

One overridden region, addressable by its name — part="piece top-left" and [data-region] both reach it.

content

.xtyle-nine-patch__content

Whatever was slotted in, held clear of the frame by inset.

Tokens & coverage

What the component consumes, checked live against what the algorithm produces.

Success:fully covered 0/0 consumed tokens produced default register: 310 tokens

Live coverage check against the xtyle-default register (derive(xtyleDefault, { anchors })coverComponent(manifest, register)). Every token this component consumes must be a key the algorithm produces.

Slots

default
html svelte astro

The content the frame surrounds.

Accessibility

The frame is decorative (aria-hidden) and takes no pointer events, so it never intercepts a click meant for the content.
Content sits in its own layer above the artwork, so a tint masks the frame without masking the text.
Because the frame is artwork rather than a border, contrast is the author's to check — tint pointed at a derived token keeps it moving with the theme rather than drifting from it.

Code

Inline SVG, a file, and a tinted frame

The same component fed markup, a file, and a token-tinted mask.

<!-- Inline SVG, cut 32px in from each edge, tinted from a token. Paste this as-is. -->
<xtyle-nine-patch slice="32" repeat="repeat" tint="var(--accent)" inset="2.25rem" src='<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96"><path fill="#000" d="M32 8A8 8 0 0 1 48 8A8 8 0 0 1 64 8A24 24 0 0 1 88 32A8 8 0 0 1 88 48A8 8 0 0 1 88 64A24 24 0 0 1 64 88A8 8 0 0 1 48 88A8 8 0 0 1 32 88A24 24 0 0 1 8 64A8 8 0 0 1 8 48A8 8 0 0 1 8 32A24 24 0 0 1 32 8Z"/></svg>'>
	<p>The corners hold their size. Only the runs between them grow.</p>
</xtyle-nine-patch>

<!-- The same three attributes over a file: raster and vector are equally at home -->
<xtyle-nine-patch src="/frames/parchment.png" slice="32" repeat="repeat" fill inset="2.25rem">
	<p>A URL is sliced exactly the same way.</p>
</xtyle-nine-patch>

<!-- One region overridden: the base still draws the other eight -->
<xtyle-nine-patch
	src="/frames/parchment.png"
	slice="32"
	pieces='{"top-left":"/frames/crest.svg"}'
	tints='{"top-left":"var(--accent)"}'
	inset="2.25rem"
>
	<p>A crest on one corner, the base everywhere else.</p>
</xtyle-nine-patch>