Skip to content
Foundations

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.

Erbil
<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.

cookie-9
<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.

Erbil
Erbil
Duhok
Duhok
Basra
Basra
Najaf
Najaf
<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.

Deploying…
<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.

Kirkuk
<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" />