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' Examples
Basic
Three fields and a binding. The preview, the copyable link, the QR code and seven working destinations are all derived from them.
<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.
<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.
<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.
<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 intargets. Both sit in the same sheet. - A recents row.
peopleputs faces above the apps, each badged with the channel the message goes over. An unreachable recipient stays on the rail asdisabled. - 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
readonlyInput. - 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
titleandtext. They are the preview's two lines and the payload every destination gets. - Give
imagewhen there is a thumbnail: a product shot, an og:image, an avatar. - Keep the default
targetsfor a public link. Narrow them only when a destination is wrong for the content, like an internal invoice on Facebook. - Give every
ShareTargetandShareActiona stableid.onsharereports it and the{#each}keys on it. - Set
doneon an action whose result leaves nothing on screen, like revoking a link. The row says it where it was pressed; no toast. - Keep
nativeon. On a phone the platform sheet reaches apps the web cannot, and it only appears where the browser has one.
Don't
- Pass
filesand expect a web intent to carry them. Only the platform sheet takes attachments; every other destination gets the link. - Raise a toast from an
onselectthat 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-tintis the brand colour and--share-inkwhat 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 bindableDefaults 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 comfortablecompact
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.