Skip to content
omaris

Containment

Share

The share sheet — a bottom sheet on a phone, a centred dialog on a desktop, built on Dialog so the focus trap, the drag-to-dismiss and the morphing height all come for free.

import { Share } from 'omaris'
Learn

Examples

Basic

Three fields and a binding. The preview, the copyable link, the QR code and seven working destinations are all derived from them.

Share

A bottom sheet on a phone, a dialog on a desktop, and nothing to wire.

omaris.dev/docs/components/share

Share via

<script lang="ts">	import { Button, Share } from 'omaris';​	let open = $state(false);</script>​<Button onclick={() => (open = true)}>Share this page</Button>​<Share	bind:open	url="https://omaris.dev/docs/components/share"	title="Share"	text="A bottom sheet on a phone, a dialog on a desktop, and nothing to wire."/>

People

people puts a row of faces above the apps, each badged with the channel it would send over. Someone with nothing to send to stays on the rail, unpressable, rather than disappearing from it.

Q3 revenue

Up 14% on Q2, with Erbil carrying most of it.

omaris.dev/reports/q3

Send to

Share via

<script lang="ts">	import { Button, Share, shareGlyphs, type ShareTarget } from 'omaris';​	let open = $state(false);	/** Who it went to. The row is the record, so nothing else announces it. */	let sentTo = $state<string | null>(null);​	const people: ShareTarget[] = [		{			id: 'rania',			label: 'Rania',			detail: 'WhatsApp',			tint: '#25d366',			glyph: shareGlyphs.bubble,			onselect: () => {				sentTo = 'Rania';			}		},		{			id: 'dilan',			label: 'Dilan',			detail: 'Telegram',			tint: '#2aabee',			glyph: shareGlyphs.plane,			onselect: () => {				sentTo = 'Dilan';			}		},		{			id: 'karwan',			label: 'Karwan',			detail: 'Email',			glyph: shareGlyphs.mail,			onselect: () => {				sentTo = 'Karwan';			}		},		{ id: 'hemin', label: 'Hemin', detail: 'Offline', disabled: true }	];</script>​<Button variant="tonal" onclick={() => (open = true)}>Send the report</Button>​{#if sentTo}	<p class="mt-3 text-body-sm text-muted-foreground">Sent to {sentTo}.</p>{/if}​<Share	bind:open	url="https://omaris.dev/reports/q3"	title="Q3 revenue"	text="Up 14% on Q2, with Erbil carrying most of it."	{people}	targets={['whatsapp', 'telegram', 'email']}/>

Your own targets

targets takes built-in names and your own objects in the same array, and actions are the rows underneath it. Revoking the link is the one press whose result would otherwise be invisible, so the row says it — and the link bar goes.

Invoice INV-2291

Due 30 September.

omaris.dev/invoices/INV-2291

Share via

<script lang="ts">	import { Button, Share, shareGlyphs, shareTargets, type ShareTarget } from 'omaris';​	let open = $state(false);	let revoked = $state(false);​	const intranet: ShareTarget = {		id: 'intranet',		label: 'Intranet',		tint: 'var(--primary)',		ink: 'var(--primary-foreground)',		glyph: shareGlyphs.globe,		href: (data) => `https://intranet.example.com/post?url=${encodeURIComponent(data.url ?? '')}`	};</script>​<Button variant="tonal" onclick={() => (open = true)}>Send invoice</Button>​<Share	bind:open	heading="Send invoice"	url="https://omaris.dev/invoices/INV-2291"	title="Invoice INV-2291"	text={revoked ? 'The link has been revoked.' : 'Due 30 September.'}	link={!revoked}	qr={!revoked}	targets={['whatsapp', 'email', shareTargets.telegram, intranet]}	actions={[		{ id: 'pdf', label: 'Download PDF', detail: '184 KB', glyph: shareGlyphs.download },		{			id: 'revoke',			label: revoked ? 'Issue a new link' : 'Revoke this link',			// Re-issuing brings the link bar back, which says it by itself.			done: revoked ? undefined : 'Link revoked',			glyph: shareGlyphs.cross,			destructive: !revoked,			onselect: () => {				revoked = !revoked;			}		}	]}/>

Overridden

The overridden case: a wider dialog, density="compact", no destination rail at all, and a consumer's classes on three separate parts. class still covers the dialog itself.

Fleet dashboard

Live, and shared read-only.

omaris.dev/dashboards/fleet
<script lang="ts">	import { Button, Share, shareGlyphs } from 'omaris';​	let open = $state(false);</script>​<Button variant="outlined" onclick={() => (open = true)}>Share a snapshot</Button>​<Share	bind:open	heading="Snapshot"	url="https://omaris.dev/dashboards/fleet"	title="Fleet dashboard"	text="Live, and shared read-only."	size="lg"	density="compact"	targets={false}	class="max-w-xl"	classes={{		thumb: 'rounded-full bg-tertiary-container text-tertiary-container-foreground',		link: 'bg-primary-container',		actions: 'bg-transparent ring-1 ring-border'	}}	actions={[		{ id: 'png', label: 'Save as PNG', detail: '1200 × 630', glyph: shareGlyphs.download },		{ id: 'open', label: 'Open in a new tab', glyph: shareGlyphs.globe }	]}/>

When to use it

Use it for

  • Handing a link to another person or app. One tag is the whole setup: <Share bind:open url={link} title={name} text={summary} />. The preview, the copyable link, the QR code, seven destinations and the platform sheet come from those three fields.
  • Sending a record out of a dashboard, like an invoice or a report. Put the exports in actions (Download PDF, Print) and the destinations in targets. Both sit in the same sheet.
  • A recents row. people puts faces above the apps, each badged with the channel the message goes over. An unreachable recipient stays on the rail as disabled.
  • Getting a URL onto a phone in the room. The QR row is on whenever there is a url, and the sheet morphs to the code in place.
  • Your own destination beside the built-ins: targets={['whatsapp', 'email', intranet]}. Names and objects share the array. { ...shareTargets.email, label: 'Send by post' } keeps a built-in's glyph and changes its wording.

Not for

  • Feedback about something that already happened → Toast. Share reports its own copy on the row that was pressed.
  • A form, a filter panel or anything you work in → Sheet.
  • A question that must be answered before anything continues → Dialog.
  • A few commands off a button → Menu.
  • Showing a link to be read rather than sent → Code Block, or a readonly Input.
  • A QR code as the point of the screen, like a table booking or a Wi-Fi handover → QR Code on the page itself.

Do

  • Fill title and text. They are the preview's two lines and the payload every destination gets.
  • Give image when there is a thumbnail: a product shot, an og:image, an avatar.
  • Keep the default targets for a public link. Narrow them only when a destination is wrong for the content, like an internal invoice on Facebook.
  • Give every ShareTarget and ShareAction a stable id. onshare reports it and the {#each} keys on it.
  • Set done on an action whose result leaves nothing on screen, like revoking a link. The row says it where it was pressed; no toast.
  • Keep native on. On a phone the platform sheet reaches apps the web cannot, and it only appears where the browser has one.

Don't

  • Pass files and expect a web intent to carry them. Only the platform sheet takes attachments; every other destination gets the link.
  • Raise a toast from an onselect that opens an app. The app opening is the report.
  • Use it as a menu. Two rows and no link is a Menu.
  • Put a destructive action anywhere but last, or without destructive: true.
  • Set closeOnShare={false} unless the sheet stays useful after a send. It fits a recents row people send to twice, and nothing else.

Quick reference

density
  • comfortable (default)
  • compact

Roomier tiles and rows for a sheet that is mostly thumbed.

API

Share

The share sheet — a bottom sheet on a phone, a centred dialog on a desktop, built on Dialog so the focus trap, the drag-to-dismiss and the morphing height all come for free.

Sharing is three fields — a link, a headline, a sentence — so that is the whole required API, and it is navigator.share()'s own shape:

That alone gives a preview of what is being shared, a link bar that copies and says so in place, seven working destinations with their intent URLs already written, a QR code for handing the link to a phone across the table, and the platform's own sheet where the browser has one. Nothing to wire.

Everything is then replaceable a piece at a time. targets takes names and objects in the same array, so keeping the defaults and adding one of your own is targets={['whatsapp', 'email', mattermost]}. people puts a row of faces above the apps — recent recipients, teammates, devices — each with the channel it would send over badged on the avatar. actions are the rows underneath: save, print, revoke the link.

It reports what it did where the result would otherwise be invisible and nowhere else: copying says "Copied" on the row that was pressed, and opening WhatsApp says nothing, because WhatsApp opened.

Keyboard: Tab reaches every row, the arrow keys walk each rail (and follow the writing direction), Home and End jump to its ends, Escape closes.

import { Share } from 'omaris'
<Share bind:open url={link} title="Q3 report" text="Revenue is up 14%." />

Parts

Every element this renders is reachable from outside. Pass Tailwind for one part as classes={{ part: '…' }}; class covers the root and is merged last, so it always wins.

root
The dialog itself — the box the whole sheet is drawn in.
surface
Everything inside it, as one column.
header
Preview row: what is being shared, and the way out.
thumb
The square at its start — a thumbnail, or a glyph.
heading
What is being shared.
subheading
The line under it — your own text, or the link, tidied.
close
The close button.
link
The copyable link bar.
linkText
The link inside it, shortened to host and path.
copy
The press that copies it.
section
A labelled block — the people rail, the apps rail.
label
Its caption, and the rail's accessible name.
rail
The horizontal scroller. It fades at whichever edge still has something past it, so a rail that scrolls says so without a scrollbar.
target
One destination.
tile
The coloured square. --share-tint is the brand colour and --share-ink what sits on it, both written per tile — which is how one component paints a WhatsApp green and a theme role with the same class.
targetLabel
Under the tile. Two lines at most, then it clips.
targetDetail
The quieter second line — a device, a last-shared time.
person
A person's circular tile, in place of tile.
badge
The channel badge on it — the app the message would go through.
actions
The list of rows under the rails.
action
One row.
actionLabel
Its label.
actionDetail
Right-hand meta, before the glyph.
actionIcon
The trailing glyph.
qr
The QR panel, once the code has been asked for.
qrPlate
The plate the code is drawn on. White in both themes on purpose: a scanner wants dark modules on a light field, and an inverted code is one half the phones in a room cannot read.
footer
Pinned under everything the sheet offers.

Props

open bindable

Defaults to false

boolean

Bindable.

url
string

The link being shared. Everything else has a sensible default from it.

title
string

Headline — the page, product or file. Also the preview's first line.

text
string

The sentence that goes with it, and the preview's second line.

files
File[]

Attachments for the platform sheet. Web intents get the link instead.

image
string

A thumbnail of what is being shared.

heading

Defaults to 'Share'

string

The sheet's own headline, above the preview. Defaults to "Share".

targets

Defaults to DEFAULT_TARGETS

readonly (ShareTargetId | ShareTarget)[] | false

Destinations, by name or as objects, in one array. Defaults to the seven built-ins; false drops the rail entirely.

people
readonly ShareTarget[]

Faces above the apps — recent recipients, teammates, devices.

actions
readonly ShareAction[]

Rows under the rails. The QR row is added to them, not instead of them.

peopleLabel

Defaults to 'Send to'

string

Caption over the people rail, and its accessible name.

targetsLabel

Defaults to 'Share via'

string

Caption over the apps rail, and its accessible name.

link
boolean

Show the copyable link bar. On whenever there is a url.

qr
boolean

Offer the QR code row. On whenever there is a url.

native

Defaults to true

boolean

Offer the platform's own sheet where the browser has one. On by default.

nativeLabel

Defaults to 'More'

string

What that tile is called.

closeOnShare

Defaults to true

boolean

Close the sheet once a destination has been handed the payload. On by default.

size

Defaults to 'md'

ShareSize
density

Defaults to 'comfortable'

ShareDensity
comfortable
compact
responsive

Defaults to true

boolean

Become a bottom sheet on a phone. On by default.

mobileQuery
string

What counts as a phone.

dismissible

Defaults to true

boolean

Escape, a press outside and a downward drag close it.

closeLabel

Defaults to 'Close'

string

Accessible name for the close button.

onshare
(id: string, data: ShareData) => void

Fires with the id of whatever was pressed, once it has been acted on.

onclose
() => void

Fires after the sheet has finished closing.

class
string
classes
ShareClasses

Per-part Tailwind overrides. class still covers the root.

thumb
Snippet

Replaces the preview square — a file icon, a colour swatch, a chart.

children
Snippet

Extra content between the link bar and the rails.

footer
Snippet

Pinned under everything — a visibility switch, an expiry note.