Web component
<p-modal>
Modal dialog built on the native <dialog> element
- Import
@parallelogram-js/core/components/PModal- Tag
p-modal
Playground
Release this table?
The 7pm booking for four will be offered to the waitlist.
<button type="button" data-modal data-modal-target="#release-table">Release table</button>
<p-modal id="release-table">
<h2 slot="title">Release this table?</h2>
<p>The 7pm booking for four will be offered to the waitlist.</p>
<div slot="actions">
<button type="button" data-modal-close>Keep it</button>
<button type="button" data-modal-close>Release table</button>
</div>
</p-modal>
Use the example to see the events it sends.
Release this table?
The 7pm booking for four will be offered to the waitlist.
With nothing slotted into actions the footer is left out, so a modal that only tells the reader something ends at its content. The close button in the header still closes it, as do Escape and a click on the backdrop.
Booking confirmed
Table 12 is held until 7.15pm. The details are on their way by email.
<button type="button" data-modal data-modal-target="#booking-confirmed">Show confirmation</button>
<p-modal id="booking-confirmed">
<h2 slot="title">Booking confirmed</h2>
<p>Table 12 is held until 7.15pm. The details are on their way by email.</p>
</p-modal>
Use the example to see the events it sends.
Booking confirmed
Table 12 is held until 7.15pm. The details are on their way by email.
A modal with nothing in title shows no heading at all. It is still named "Dialog" for assistive technology, which is all anyone using a screen reader will hear when it opens, so give a modal a title wherever you can.
Table 12 is held until 7.15pm.
<button type="button" data-modal data-modal-target="#held-note">Open the note</button>
<p-modal id="held-note">
<p>Table 12 is held until 7.15pm.</p>
<div slot="actions">
<button type="button" class="btn btn--primary" data-modal-close>Close</button>
</div>
</p-modal>
Use the example to see the events it sends.
Table 12 is held until 7.15pm.
A button slotted into secondary is held against the leading end of the footer while the rest stay together at the other, so the markup says where a button belongs rather than how to push it. Nothing needs styling, and with nothing in secondary the buttons sit together as they always have.
Edit booking
Table 12 on Friday at 7pm, for four.
<button type="button" data-modal data-modal-target="#edit-booking">Edit booking</button>
<p-modal id="edit-booking">
<h2 slot="title">Edit booking</h2>
<p>Table 12 on Friday at 7pm, for four.</p>
<button type="button" class="btn btn--danger" slot="secondary" data-modal-close>Cancel booking</button>
<button type="button" class="btn" slot="actions" data-modal-close>Close</button>
<button type="button" class="btn btn--primary" slot="actions" data-modal-close>Save changes</button>
</p-modal>
Use the example to see the events it sends.
Edit booking
Table 12 on Friday at 7pm, for four.
Content taller than the panel scrolls inside it, while the close button stays where it is and the footer stays with it. The body is the page's own markup, spaced by the modal rather than by the margins the browser would give it.
Booking terms
Holding your table
A table is held for fifteen minutes past the time you booked. After that we may offer it to the waitlist, and we will text the number on the booking before we do.
Changing or cancelling
Change or cancel from the link in your confirmation email, at any time up to two hours before you are due. Inside two hours, please call the restaurant instead.
- Parties of six or more: one working day's notice
- Set menus and private rooms: three working days' notice
- New Year's Eve: no changes after 20 December
Deposits
A deposit is taken for parties of eight or more and for every booking on a public holiday. It comes off the bill on the night. A deposit is returned in full when a booking is cancelled with the notice above, and kept when it is not.
Groups and events
Parties of twelve or more eat from a set menu, chosen a week ahead. The private room seats twenty-four and has a minimum spend on Fridays and Saturdays, which we will quote when you enquire.
We can hold a room for seven days without a deposit while you decide.
Children
Children are welcome until 8pm, and high chairs are free but limited, so please ask for one when you book. Half portions are available from most of the menu.
Gift vouchers
Vouchers are valid for two years from the day they are bought and can be used against any bill, including drinks. They cannot be exchanged for cash, and any balance stays on the voucher.
Your details
We keep your name, phone number and email so we can hold the booking and tell you if anything changes. We do not pass them to anyone else, and you can ask us to delete them at any time.
Where you tell us about allergies or access needs, we keep those with the booking and share them with the kitchen and the floor team.
Last updated 3 September 2026. These terms apply to bookings made through this site.<button type="button" data-modal data-modal-target="#booking-terms">Read the booking terms</button>
<p-modal id="booking-terms" data-modal-size="lg">
<h2 slot="title">Booking terms</h2>
<h3>Holding your table</h3>
<p>A table is held for fifteen minutes past the time you booked. After that we may offer it to the waitlist, and we will text the number on the booking before we do.</p>
<h3>Changing or cancelling</h3>
<p>Change or cancel from the link in your confirmation email, at any time up to two hours before you are due. Inside two hours, please call the restaurant instead.</p>
<ul>
<li>Parties of six or more: one working day's notice</li>
<li>Set menus and private rooms: three working days' notice</li>
<li>New Year's Eve: no changes after 20 December</li>
</ul>
<h3>Deposits</h3>
<p>A deposit is taken for parties of eight or more and for every booking on a public holiday. It comes off the bill on the night. A deposit is returned in full when a booking is cancelled with the notice above, and kept when it is not.</p>
<h3>Groups and events</h3>
<p>Parties of twelve or more eat from a set menu, chosen a week ahead. The private room seats twenty-four and has a minimum spend on Fridays and Saturdays, which we will quote when you enquire.</p>
<p>We can hold a room for seven days without a deposit while you decide.</p>
<h3>Children</h3>
<p>Children are welcome until 8pm, and high chairs are free but limited, so please ask for one when you book. Half portions are available from most of the menu.</p>
<h3>Gift vouchers</h3>
<p>Vouchers are valid for two years from the day they are bought and can be used against any bill, including drinks. They cannot be exchanged for cash, and any balance stays on the voucher.</p>
<h3>Your details</h3>
<p>We keep your name, phone number and email so we can hold the booking and tell you if anything changes. We do not pass them to anyone else, and you can ask us to delete them at any time.</p>
<p>Where you tell us about allergies or access needs, we keep those with the booking and share them with the kitchen and the floor team.</p>
<small>Last updated 3 September 2026. These terms apply to bookings made through this site.</small>
<div slot="actions">
<button type="button" data-modal-close>Decline</button>
<button type="button" class="btn btn--primary" data-modal-close>Accept</button>
</div>
</p-modal>
Use the example to see the events it sends.
Booking terms
Holding your table
A table is held for fifteen minutes past the time you booked. After that we may offer it to the waitlist, and we will text the number on the booking before we do.
Changing or cancelling
Change or cancel from the link in your confirmation email, at any time up to two hours before you are due. Inside two hours, please call the restaurant instead.
- Parties of six or more: one working day's notice
- Set menus and private rooms: three working days' notice
- New Year's Eve: no changes after 20 December
Deposits
A deposit is taken for parties of eight or more and for every booking on a public holiday. It comes off the bill on the night. A deposit is returned in full when a booking is cancelled with the notice above, and kept when it is not.
Groups and events
Parties of twelve or more eat from a set menu, chosen a week ahead. The private room seats twenty-four and has a minimum spend on Fridays and Saturdays, which we will quote when you enquire.
We can hold a room for seven days without a deposit while you decide.
Children
Children are welcome until 8pm, and high chairs are free but limited, so please ask for one when you book. Half portions are available from most of the menu.
Gift vouchers
Vouchers are valid for two years from the day they are bought and can be used against any bill, including drinks. They cannot be exchanged for cash, and any balance stays on the voucher.
Your details
We keep your name, phone number and email so we can hold the booking and tell you if anything changes. We do not pass them to anyone else, and you can ask us to delete them at any time.
Where you tell us about allergies or access needs, we keep those with the booking and share them with the kitchen and the floor team.
Last updated 3 September 2026. These terms apply to bookings made through this site.About
Opening calls showModal(), so the dialog renders in the top layer, the rest of the page is inert and focus stays inside it. The dialog is named after its title slot. Escape and the backdrop close it unless its settings say otherwise, focus returns to the element that had it before opening once no other modal is still open, and page scroll stays locked while any modal is open.
Open it with the open attribute, the open() method, or a [data-modal] trigger handled by the Modal component.
Accessibility
The dialog is modal, named by its title, and returns focus when it closes. Give every modal a title.
Without JavaScript
The title, content and actions are the page's own markup, so before the module loads they are ordinary elements sitting in the flow rather than a dialog. The package stylesheet hides the element while scripts are running, so that content does not appear inline for the moment before it upgrades; with scripting off it stays visible and readable, which is better than content nothing is coming to bring back.
Usage
Register the component and the framework loads it the first time a page contains p-modal.
import { Parallelogram } from '@parallelogram-js/core';
const app = Parallelogram.create();
app.components.add('p-modal', () => import('@parallelogram-js/core/components/PModal'));
app.run();
Attributes
| Name | Type | Default | Description |
|---|---|---|---|
open | flag | none | Present while the modal is open; add or remove it to open or close the modal |
close-label | string | Close | The accessible name of the close button For every instance, PModal.defaults.closeLabel. |
data-modal-size | xs | sm | md | lg | xl | fullscreen | md | The panel width |
data-modal-closable | boolean | true | false hides the close button and ignores Escape and the backdrop; data-modal-close buttons still close the modal |
data-modal-backdrop-close | boolean | true | Close when the backdrop is clicked |
data-modal-keyboard | boolean | true | Close with Escape |
data-modal-stateset by the component | closed | opening | open | closing | none | The modal's state |
Methods
| Name | Signature | Description |
|---|---|---|
open | (options?: { returnFocus?: HTMLElement | null }) => void | Open the modal; returnFocus is the element to focus when it closes, or null to leave focus alone |
close | () => void | Close the modal once its closing animation has finished |
toggle | (force?: boolean) => void | Open or close the modal |
Events
| Name | Detail | Description |
|---|---|---|
p-modal:open | { modal: HTMLElement } | The modal opened; bubbles out of shadow roots DOM event |
p-modal:close | { modal: HTMLElement } | The modal has closed; bubbles out of shadow roots DOM event |
Slots
| Name | Description |
|---|---|
title | The title, which also names the dialog |
| default | The content |
secondary | A secondary action, such as deleting what the modal is about, held against the leading end of the footer and away from the buttons in actions; the footer shows while either slot has something in it |
actions | Footer buttons; any element with data-modal-close closes the modal |
Shadow parts
| Name | Description |
|---|---|
panel | The <dialog>; style the page behind with ::part(panel)::backdrop |
header | The title row |
title | The title container |
close | The close button |
content | The scrolling content area |
footer | The actions row |
CSS custom properties
| Name | Default | Description |
|---|---|---|
--modal-panel-border | var(--surface-dialog-border-color) | Panel border colour; follows the dialog surface |
--modal-border-width | var(--surface-dialog-border-width) | Panel border width; follows the dialog surface |
--modal-shadow-color | var(--color-shadow) | Colour the panel shadow is built from |
--modal-header-bg | var(--surface-dialog-color-bg) | Header background; follows the dialog surface |
--modal-header-padding | var(--panel-header-padding) | Header padding; follows the panel family |
--modal-footer-bg | var(--surface-dialog-color-bg) | Footer background; follows the dialog surface |
--modal-footer-padding | var(--panel-footer-padding) | Footer padding; follows the panel family |
--modal-content-padding | var(--panel-padding) | Padding around the content; follows the panel family, and sets the close button inset |
--modal-gap | 0.5em | Space between the title and the close button |
--modal-close-size | 1.75em | Size of the close button, which sets the least header height |
--modal-close-hover-bg | var(--color-hover) | Close button background on hover |
--modal-btn-bg | var(--button-secondary-bg) | Slotted button background; follows the secondary button |
--modal-btn-color | var(--button-secondary-color) | Slotted button text; follows the secondary button |
--modal-btn-border | var(--button-secondary-border) | Slotted button border; follows the secondary button |
--modal-btn-hover-bg | var(--button-secondary-hover-bg) | Slotted button background on hover |
--modal-btn-hover-border | var(--button-secondary-hover-border) | Slotted button border on hover |
--modal-btn-min-height | 3.125em | Least height of a slotted button |
--modal-btn-primary-bg | var(--button-primary-bg) | A .primary slotted button background; set the button token to change every component, or this one for modals alone |
--modal-btn-primary-color | var(--button-primary-color) | A .primary slotted button text colour |
--modal-btn-primary-hover-bg | var(--button-primary-hover-bg) | A .primary slotted button background on hover |
--modal-btn-danger-bg | var(--button-danger-bg) | A .danger slotted button background |
--modal-btn-danger-color | var(--button-danger-color) | A .danger slotted button text colour |
--modal-btn-danger-hover-bg | var(--button-danger-hover-bg) | A .danger slotted button background on hover |
--modal-max-width | 92vw | Most width the panel takes, whatever size it is given |
--modal-fullscreen-inset | 3em | Space left around a fullscreen modal |
--modal-transition | 0.2s ease | Length and easing of hover and focus changes inside the panel |
--modal-disabled-opacity | none | Opacity of a disabled control in the panel |
--modal-size-xs | 18.75em | Width of size='xs' |
--modal-size-md | 40em | Width of size='md' |
--modal-size-lg | 50em | Width of size='lg' |
--modal-size-xl | 62.5em | Width of size='xl' |
--modal-animation-duration | 0.2s | Length of the opening and closing animations |
--modal-animation-easing | none | Easing of the animations |
--modal-backdrop-bg | color-mix(in srgb, var(--color-surface) 95%, transparent) | Colour of the page behind the modal; the page surface at 95%, so white in light mode and the dark surface in dark mode |
--modal-panel-bg | var(--surface-dialog-color-bg) | Panel background |
--modal-panel-color | var(--surface-dialog-color-text) | Panel text colour |
--modal-radius | none | Panel corner radius |
--modal-shadow | var(--surface-dialog-shadow) | Panel shadow |
--modal-close-inset | var(--modal-padding-y) | Distance from the top of the panel to the close button, and the least height of the header, so the title sits on the button's line |
--modal-content-gap | var(--modal-space-xl) | Space between the elements the page puts in the modal, which give up the margins the browser would give them so the content meets the panel's padding |
--modal-close-inset-x | var(--modal-content-padding) | Distance from the side of the panel to the close button, following the content's own padding so the button lines up with what sits under it; set it where a page gives the panel padding more than one value |
--modal-padding-x | none | Horizontal padding |
--modal-padding-y | none | Vertical padding |
--modal-max-height | none | Tallest the panel gets before its content scrolls |
--modal-size-sm | none | Width of the sm size; xs, md, lg and xl match |