Shape
The Material 3 shape library, as a box you can put things in.
Walkthrough
1. Basic
A shape is a box that clips what is in it — a colour, an icon, a photo. Give it a size and a background, and it is done.
<script lang="ts"> import { Shape } from 'omaris';</script><Shape shape="cookie-9" size="5rem" class="bg-primary" /><Shape shape="clover-4" size="5rem" class="bg-tertiary" /><Shape shape="arch" size="5rem" src="/docs/photos/erbil.svg" alt="Erbil" /><Shape shape="burst" size="5rem" class="bg-tertiary-container text-tertiary-container-foreground"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8"> <path d="m12 3 2.6 6.1 6.4.5-4.9 4.2 1.5 6.2L12 16.8 6.4 20l1.5-6.2L3 9.6l6.4-.5z" /> </svg></Shape> 2. Library
All thirty-six of them. Pick one and the big shape travels there from wherever it is — every pair morphs, because the morph never looks at the path data. shapeNames and shapePaths are exported too, if you want the outline without the component.
<script lang="ts"> import { Shape, Text, shapeNames, type ShapeName } from 'omaris'; let picked = $state<ShapeName>('cookie-9');</script><div class="flex w-full flex-col items-center gap-6"> <Shape shape={picked} size="9rem" class="bg-primary" /> <Text variant="label-lg" tone="muted">{picked}</Text> <div class="grid w-full grid-cols-6 gap-3 sm:grid-cols-9 md:grid-cols-12"> {#each shapeNames as name (name)} <button type="button" onclick={() => (picked = name)} aria-label={name} aria-pressed={picked === name} class="rounded-shape-sm p-1 outline-offset-2 focus-visible:outline-2 focus-visible:outline-primary" > <Shape shape={name} hover="circle" size="2.25rem" class={picked === name ? 'bg-primary' : 'bg-muted-foreground/35'} /> </button> {/each} </div></div> 3. Morphing
hover and press are shapes, not states: the outline travels to them and back, on MD3's emphasized curve. Interrupt one halfway — leave while it is still moving — and it turns around from where it is rather than jumping.
<script lang="ts"> import { Shape } from 'omaris';</script><Shape shape="square" hover="cookie-12" size="6rem" class="bg-primary" /><Shape shape="heart" hover="pixel-circle" size="6rem" class="bg-tertiary" /><Shape shape="pill" hover="burst" press="circle" size="6rem" class="bg-primary-container" /><Shape shape="circle" hover="clover-4" duration={1200} size="6rem" class="bg-tertiary-container" /> 4. Media
The reason to have these: a picture in something other than a rounded rectangle. src clips the image itself, so object-cover and the shape are one thing, and the morph on hover leaves the photo exactly where it was.
<script lang="ts"> import { Shape, Text } from 'omaris'; const PLACES = [ { name: 'Erbil', shape: 'cookie-7', hover: 'circle' }, { name: 'Duhok', shape: 'clover-4', hover: 'cookie-12' }, { name: 'Basra', shape: 'arch', hover: 'pill' }, { name: 'Najaf', shape: 'ghostish', hover: 'square' } ] as const;</script>{#each PLACES as place (place.name)} <figure class="flex flex-col items-center gap-2"> <Shape shape={place.shape} hover={place.hover} size="7rem" src="/docs/photos/{place.name.toLowerCase()}.svg" alt={place.name} /> <Text as="figcaption" variant="label-md" tone="muted">{place.name}</Text> </figure>{/each} 5. Loading
cycle walks a list of shapes forever, and spin turns the outline while the contents counter-rotate on the same clock — which is why the tick inside the last one never leans. Together they are MD3's loading indicator; both stop under prefers-reduced-motion.
<script lang="ts"> import { Shape, Text } from 'omaris';</script><div class="flex items-center gap-3"> <Shape cycle={['cookie-9', 'clover-4', 'pill', 'soft-burst']} spin={6} interval={900} size="2.5rem" class="bg-primary" /> <Text variant="label-lg">Deploying…</Text></div><Shape shape="flower" spin={12} size="2.5rem" class="bg-tertiary" /><Shape cycle={['circle', 'square', 'diamond', 'hexagon']} size="2.5rem" class="bg-primary-container"/><Shape shape="cookie-12" spin={8} size="2.5rem" class="bg-success-container text-success-container-foreground"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4"> <path d="m5 13 4 4 10-10" stroke-linecap="round" stroke-linejoin="round" /> </svg></Shape> 6. Overridden
The overridden case. A size-* class beats the size prop; a box that is not square stretches the outline with it, on purpose — the clip is in the box's own space. classes reaches the parts: here the picture is pushed around inside a shape that has not moved.
<script lang="ts"> import { Shape } from 'omaris';</script><Shape shape="cookie-9" size="4rem" class="size-28 bg-primary" /><Shape shape="cookie-9" size="4rem" class="h-20 w-44 max-w-full bg-tertiary" /><Shape shape="arch" size="7rem" src="/docs/photos/kirkuk.svg" alt="Kirkuk" classes={{ image: 'scale-150 object-left' }}/><Shape shape="pill" class="size-20 bg-linear-to-br from-primary to-tertiary shadow-3" />