Nine Patch
Artwork sliced into a frame that scales without smearing its corners.
Live demo
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.
Props
10 props, straight from the manifest.
| Prop | Type | Default | Bindings | Description |
|---|---|---|---|---|
Appearance
States
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
The sliced artwork, drawn as its own layer so a tint can mask it without masking the content.
piece
One overridden region, addressable by its name — part="piece top-left" and [data-region] both reach it.
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.
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
The content the frame surrounds.
Accessibility
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>
<script lang="ts">
import { NinePatch } from "@xtyle/svelte";
const frame = `<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>`;
const crest = `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path fill="#000" fill-rule="evenodd" d="M16 2 29 9v13l-13 8-13-8V9zm0 6-7 4v8l7 5 7-5v-8z"/></svg>`;
</script>
<NinePatch src={frame} slice="32" repeat="repeat" tint="var(--accent)" inset="2.25rem">
<p>A themed surface from one monochrome drawing.</p>
</NinePatch>
<NinePatch
src={frame}
slice="32"
repeat="repeat"
tint="var(--fg-2)"
pieces={{ "top-left": crest, "top-right": crest }}
tints={{ "top-left": "var(--accent)", "top-right": "var(--accent)" }}
inset="2.25rem"
>
<p>Two corners swapped, the rest still the base.</p>
</NinePatch>
---
import NinePatch from "@xtyle/astro/NinePatch.astro";
const frame = `<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>`;
---
<NinePatch src={frame} slice="32" repeat="repeat" tint="var(--accent)" inset="2.25rem">
<p>Shape from the art, colour from the algorithm.</p>
</NinePatch>
<NinePatch src="/frames/parchment.png" slice="32" repeat="repeat" fill inset="2.25rem">
<p>A file works the same way.</p>
</NinePatch>