Actions
An action sheet offers a short list of things a person can do next, anchored to the bottom of the screen where a thumb can reach it.
When to use it
Use an action sheet when a single tap needs to branch into several choices. Share, which opens Copy link, Send a copy, Save to Files. A row’s overflow button, which opens Rename, Duplicate, Delete.
Use an alert when there is one decision with consequences, not a menu of options. Use a plain button when there is only one thing to do.
Keep the list short. Seven rows is the practical ceiling before it stops reading as a list of choices and starts reading as a screen.
Anatomy
| Part | Value |
|---|---|
| Width | 300px, capped at 100vw - 32px |
| Radius | 34px, a plain arc |
| Surface | The alert’s overlay glass: rgba(247,247,247,0.73) light, rgba(26,26,26,0.9) dark, blur 30px, saturate 180%, with the same hairline, rims and glow |
| Shadow | 0 8px 48px rgba(0,0,0,0.25) (--p-shadow-overlay) |
| Padding | 14px, plus max(16px, env(safe-area-inset-bottom)) below the panel |
| Header | Left aligned; the title’s first line 22px from the top and left edges, 24px above the first row |
| Title | 17px, weight 590, line height 22px, tracking -0.43px |
| Message | 17px, weight 400, same color as the title, 10px below it |
| Row | 272px wide at the default width, 48px tall, 24px radius, label 17px at weight 590 |
| Row fill | rgba(0,0,0,0.11) light, rgba(255,255,255,0.065) dark |
| Row gap | 8px, and 8px extra above Cancel |
| Entry | 0.28s slide from 16px below, cubic-bezier(0.22, 1, 0.36, 1) |
The rows are capsules stacked with a 10px gap, not a divided list with hairlines between the items. That is the current iOS treatment, and it is the same 48px capsule the alert uses for its actions.
The header is left aligned while the rows are centered. The alert centers both, because an alert asks a question. An action sheet labels a list, so the header reads as a caption over it.
Try it
Escape closes it, Tab is trapped inside it, focus returns to the button that opened it, and a tap outside dismisses it. The action sheet is a .p-modal, so all of that comes from the shared manager rather than from anything sheet-specific.
Rows
| Class | Appearance | Use it for |
|---|---|---|
.p-action-sheet__action | Quiet fill, label color | Every ordinary choice |
.p-action-sheet__action--destructive | Quiet fill, red label | Deleting or removing something |
.p-action-sheet__cancel | Same fill, 8px of separation above | Dismissing without choosing |
Cancel goes last and gets the extra gap. The separation matters more than it looks: it stops a thumb aiming for Cancel from landing on the destructive row directly above it.
Destructive rows keep the quiet fill and only turn the label red, the same rule the alert follows. Put a destructive row near the bottom of the list, above Cancel, not at the top where a reflex tap lands.
Label rows with verbs. Copy link, Send a copy, Remove sharing. A row labelled with a noun makes people guess what tapping it does.
Markup
<div class="p-modal-background">
<div class="p-modal p-action-sheet" id="share-sheet"
data-p-close-on-outside="true"
role="dialog" aria-modal="true" aria-hidden="true"
aria-labelledby="share-sheet-title">
<div class="p-action-sheet__surface">
<div class="p-action-sheet__header">
<h2 class="p-action-sheet__title" id="share-sheet-title">Share “Q3 Report”</h2>
<p class="p-action-sheet__message">
Anyone with the link will be able to view this file.
</p>
</div>
<div class="p-action-sheet__actions">
<button class="p-action-sheet__action" type="button">Copy link</button>
<button class="p-action-sheet__action p-action-sheet__action--destructive"
type="button">Remove sharing</button>
<button class="p-action-sheet__action p-action-sheet__cancel"
type="button" data-p-cancel>Cancel</button>
</div>
</div>
</div>
</div>
<button class="p-button p-button-touch p-button-primary"
data-p-open-modal="#share-sheet">Share</button>
The header is optional. Drop .p-action-sheet__header when the rows explain themselves, which is most of the time.
role="dialog" here, not role="alertdialog". An action sheet offers choices; it does not interrupt with something that must be acknowledged.
Dark mode
The surface moves to the dark overlay glass, rgba(26,26,26,0.9), rows become a faint lift of rgba(255,255,255,0.065), and the destructive red lightens from #FF383C to #FF5257. The blur is unchanged.
Accessibility
Every row is a real <button>, so the list is navigable by keyboard and announced as a set of buttons. Rows that navigate rather than act should be <a href> instead.
The panel clears the home indicator through env(safe-area-inset-bottom), falling back to 16px where the environment variable resolves to zero.
Reduced motion removes the slide and leaves a 0.25s fade. Reduced transparency and browsers without backdrop-filter both get an opaque surface, because text over a translucent panel on arbitrary content cannot be guaranteed to meet 4.5:1.
The 34px corner is a plain arc, as iOS draws it, matching the alert. Increased contrast swaps the glass edge for a solid 1px keyline over an opaque surface.
Share sheets
A share sheet offers somewhere to send the thing on screen: a person, an app, or a destination like Files.
It is mostly a composition of parts that already exist. The panel is a bottom sheet and the action groups under the targets are inset lists. Both already carry the radii this sheet uses, 38px and 26px. What .p-activity-view adds is the target row.
Q3 Report
PDF Document, 2.4 MB
- Copy
- Save to Files
<div class="p-activity-view">
<div class="p-activity-view__header">
<div>
<h2 class="p-activity-view__title">Q3 Report</h2>
<p class="p-activity-view__subtitle">PDF Document, 2.4 MB</p>
</div>
<span></span>
<button class="p-activity-view__close" type="button" aria-label="Close">
<i class="ph-bold ph-x"></i>
</button>
</div>
<div class="p-activity-view__targets">
<button class="p-activity-view__target" type="button">
<span class="p-activity-view__avatar">AL</span>
<span class="p-activity-view__target-label">Ada</span>
<span class="p-activity-view__target-detail">Messages</span>
</button>
<button class="p-activity-view__target" type="button">
<span class="p-activity-view__app"><i class="ph-bold ph-envelope-simple"></i></span>
<span class="p-activity-view__target-label">Mail</span>
</button>
</div>
</div>
| Part | Value |
|---|---|
| Panel | 402px, 38px radius, the bottom sheet’s own values |
| Header title | 17px, weight 590, tracking -0.43px |
| Header subtitle | 13px, tracking -0.1px, secondary |
| Close button | 44px circle |
| Person target | 70px circle |
| App target | 64px rounded square at 16px radius |
| Target label | 12px, tracking +0.06px |
| Target detail | 11px, secondary |
| Action groups | 26px radius, the inset list’s own value |
The target row scrolls sideways rather than wrapping, so the first few destinations stay in the same place however many there are. It bleeds to the panel edges, which is what signals there is more to the right.
People come before apps. iOS puts the person you last shared with first, because that is the most likely destination and the least work to reach.
Each target is a button with a label, so an icon-only destination still announces its name. The detail line under a person names the app the share would go through.
The legacy action sheet
The v1 .p-action-* classes are still here and still work, with their own actions.js. The v2 sheet is a separate component that runs on the modal manager instead.
| v1 | v2 |
|---|---|
.p-action-big-container | .p-modal.p-action-sheet |
.p-action-container | .p-action-sheet__surface |
.p-action-title | .p-action-sheet__title |
.p-action-neutral | .p-action-sheet__action |
.p-action-destructive | .p-action-sheet__action--destructive |
.p-action-cancel | .p-action-sheet__cancel |
actions.js | modals.js |
Adding .p-action-sheet opts the modal out of every v1 .p-modal rule, so the two can sit on the same page without interfering.