Skip to content
Containment

Device Mockup

A device drawn around whatever you put on its screen.

Walkthrough

1. Basic

A phone with your own components on the screen. The screen is laid out at the phone's real width — 390 CSS px — however small the picture is drawn, and it scrolls when there is more than fits.

<script lang="ts">	import { DeviceMockup, Text } from 'omaris';​	const TILES = [		{ label: 'Orders', value: '128' },		{ label: 'Refunds', value: '4' },		{ label: 'Visitors', value: '2,904' },		{ label: 'Signups', value: '61' }	];</script>​<DeviceMockup classes={{ screen: 'bg-surface-container-low' }}>	<div class="flex flex-col gap-4 px-5 pt-2 pb-6">		<Text variant="headline-md" as="h2">Today</Text>​		<div class="rounded-shape-lg bg-primary-container p-4 text-primary-container-foreground">			<Text variant="label-md">Revenue</Text>			<Text variant="display-sm" tabular>$48,210</Text>		</div>​		<div class="grid grid-cols-2 gap-3">			{#each TILES as tile (tile.label)}				<div class="rounded-shape-lg bg-surface-container p-4">					<Text variant="label-md" tone="muted">{tile.label}</Text>					<Text variant="title-lg" tabular>{tile.value}</Text>				</div>			{/each}		</div>	</div></DeviceMockup>

2. Every device

Six frames, each fitted to its own column with nothing measured by hand. Every one carries the real viewport of the thing it draws, so the same markup wraps like a phone in the phone and like a desktop in the laptop.

Phone
Android
Tablet
Laptop
Browser
Watch
<script lang="ts">	import { DeviceMockup, DEVICE_IDS, DEVICES, Text } from 'omaris';</script>​<div class="grid grid-cols-2 items-end gap-x-6 gap-y-10 sm:grid-cols-3">	{#each DEVICE_IDS as id (id)}		<figure class="flex flex-col items-center gap-3">			<DeviceMockup device={id} classes={{ screen: 'bg-surface-container-low' }}>				{#snippet children({ width, height })}					<div class="grid size-full place-items-center p-4 text-center">						<Text variant="title-lg" as="p" tabular>{width} × {height}</Text>					</div>				{/snippet}			</DeviceMockup>			<Text as="figcaption" variant="label-md" tone="muted">{DEVICES[id].label}</Text>		</figure>	{/each}</div>

3. Finishes

Five finishes. A wallpaper runs under the status bar and the island rather than around them, so safeArea={false} puts the clock back over the picture — and a light text-* on the glass turns the clock white with it.

graphite
silver
gold
sky
porcelain
<script lang="ts">	import { DeviceMockup, Text, type DeviceFinish } from 'omaris';​	const FINISHES: DeviceFinish[] = ['graphite', 'silver', 'gold', 'sky', 'porcelain'];</script>​<div class="grid grid-cols-2 items-end gap-6 sm:grid-cols-5">	{#each FINISHES as finish (finish)}		<figure class="flex flex-col items-center gap-3">			<DeviceMockup				{finish}				width={180}				safeArea={false}				classes={{ screen: 'bg-linear-to-br from-info to-tertiary text-white' }}			>				<div class="grid size-full place-items-center">					<Text variant="display-lg" tabular>9:41</Text>				</div>			</DeviceMockup>			<Text as="figcaption" variant="label-md" tone="muted">{finish}</Text>		</figure>	{/each}</div>

4. Responsive preview

A responsive preview anyone can drive: pick a device, turn it over. The snippet is told the width it is laid out at, and turning a phone changes the geometry rather than the picture — the viewport becomes 844 × 390, the island moves to the side and the content stays upright.

<script lang="ts">	import {		DeviceMockup,		SegmentedButton,		Text,		type DeviceId,		type DeviceOrientation	} from 'omaris';​	let device = $state<DeviceId>('phone');	let orientation = $state<DeviceOrientation>('portrait');</script>​<div class="flex flex-col items-center gap-5">	<div class="flex flex-wrap justify-center gap-3">		<SegmentedButton			label="Device"			mandatory			value={device}			onchange={(value) => (device = value as DeviceId)}			items={[				{ value: 'phone', label: 'Phone' },				{ value: 'tablet', label: 'Tablet' },				{ value: 'browser', label: 'Browser' }			]}		/>		<SegmentedButton			label="Orientation"			mandatory			value={orientation}			onchange={(value) => (orientation = value as DeviceOrientation)}			items={[				{ value: 'portrait', label: 'Portrait' },				{ value: 'landscape', label: 'Landscape', disabled: device === 'browser' }			]}		/>	</div>​	<DeviceMockup		{device}		{orientation}		width={orientation === 'landscape' || device === 'browser' ? 560 : 300}		classes={{ screen: 'bg-surface-container-low' }}	>		{#snippet children({ width })}			<div class="grid gap-3 p-5 {width < 600 ? 'grid-cols-1' : 'grid-cols-3'}">				<Text variant="headline-sm" as="h2" class="col-span-full">{width} px across</Text>				{#each ['Orders', 'Visitors', 'Signups'] as tile (tile)}					<div class="rounded-shape-lg bg-surface-container p-4">						<Text variant="label-md" tone="muted">{tile}</Text>						<Text variant="title-lg" tabular>128</Text>					</div>				{/each}			</div>		{/snippet}	</DeviceMockup></div>

5. A website inside it

A real site in a real iframe, at the device's own viewport: scroll it, follow a link. The browser window's address bar reads the site's host.

<script lang="ts">	import { DeviceMockup } from 'omaris';</script>​<DeviceMockup device="browser" src="/" url="omaris.dev" label="The omaris home page" />

6. Overridden

Every part is reachable. The frame is a colour of its own, the glass carries its own background, the width is a class — and the screen is inert, because this one is a picture rather than something to use.

<script lang="ts">	import { DeviceMockup, Text } from 'omaris';</script>​<DeviceMockup	device="tablet"	inert	class="w-80"	classes={{		frame: 'bg-primary',		button: 'bg-primary',		screen: 'bg-primary-container text-primary-container-foreground'	}}>	{#snippet children({ width, height })}		<div class="grid size-full place-items-center text-center">			<div>				<Text variant="headline-sm" as="p" tabular>{width} × {height}</Text>				<Text variant="label-md">the viewport this snippet is laid out in</Text>			</div>		</div>	{/snippet}</DeviceMockup>