<arc-modal> Overview
Guidelines
When to use
- Use for forms, settings panels, creation wizards, and rich content overlays
- Provide a clear heading that describes what the user is doing
- Always include a cancel or dismiss path so users never feel trapped
- Place the primary action button last (rightmost) in the footer
- Keep modal content concise — long scrolling modals indicate a page is needed instead
- Use the `sm` size for simple content, `md` for forms, and `lg` for tables or multi-column layouts
When not to use
- Do not use Modal for quick binary confirmations — use Dialog instead
- Do not nest modals inside other modals — use a stepped flow within a single modal instead
- Do not use a modal for passive notifications — use Toast or Alert
- Do not disable `closable` unless the workflow truly requires an explicit choice
- Do not put complex navigation or multi-page flows inside a modal
- Do not auto-open a modal on page load — this is disruptive and hurts accessibility
Features
- Automatic focus trap — Tab and Shift+Tab cycle within the dialog
- Returns focus to the trigger element on close
- Backdrop blur and dim overlay for clear visual hierarchy
- Slide-up entry and fade-out exit animations via CSS transforms
- ESC key and backdrop click close the dialog by default
- Three width presets: sm (400px), md (560px), lg (720px)
- Named `footer` slot for action buttons with built-in right-alignment
- Closable prop to disable all implicit dismiss paths for critical flows
- Fires `arc-close` event when the dialog is dismissed
- Fully accessible with `role="dialog"`, `aria-modal`, and `aria-labelledby`
Preview
Usage
This component requires JavaScript. No pure HTML/CSS version is available — use the Web Component directly or a framework wrapper.
<arc-button id="open-demo-modal" variant="secondary">Edit Profile</arc-button>
<arc-modal id="demo-modal" heading="Edit Profile" size="md">
<div style="display:flex; flex-direction:column; gap:12px;">
<label>Display Name
<arc-input value="Ada Lovelace"></arc-input>
</label>
<label>Bio
<arc-input value="Analytical engine enthusiast"></arc-input>
</label>
</div>
<div slot="footer">
<arc-button id="cancel-demo-modal" variant="ghost">Cancel</arc-button>
<arc-button id="confirm-demo-modal" variant="primary">Save Changes</arc-button>
</div>
</arc-modal>
<script>
const openBtn = document.querySelector('#open-demo-modal');
const modal = document.querySelector('#demo-modal');
openBtn.addEventListener('click', () => { modal.open = true; });
document.querySelector('#cancel-demo-modal').addEventListener('click', () => { modal.open = false; });
document.querySelector('#confirm-demo-modal').addEventListener('click', () => { modal.open = false; });
</script> import { Modal, Button, Input } from '@arclux/arc-ui-react';
import { useState } from 'react';
function EditProfileModal() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="secondary" onClick={() => setOpen(true)}>Edit Profile</Button>
<Modal open={open} heading="Edit Profile" size="md" onArcClose={() => setOpen(false)}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<label>Display Name<Input value="Ada Lovelace" /></label>
<label>Bio<Input value="Analytical engine enthusiast" /></label>
</div>
<div slot="footer">
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Save Changes</Button>
</div>
</Modal>
</>
);
} <script setup>
import { ref } from 'vue';
import { Button, Modal, Input } from '@arclux/arc-ui-vue';
const open = ref(false);
</script>
<template>
<Button variant="secondary" @click="open = true">Edit Profile</Button>
<Modal :open="open" heading="Edit Profile" size="md" @arc-close="open = false">
<form style="display:flex; flex-direction:column; gap:12px;">
<label>Display Name<Input value="Ada Lovelace" /></label>
<label>Bio<Input value="Analytical engine enthusiast" /></label>
</form>
<template #footer>
<Button variant="ghost" @click="open = false">Cancel</Button>
<Button variant="primary" @click="open = false">Save Changes</Button>
</template>
</Modal>
</template> <script>
import { Button, Modal, Input } from '@arclux/arc-ui-svelte';
let open = $state(false);
</script>
<Button variant="secondary" onclick={() => open = true}>Edit Profile</Button>
<Modal {open} heading="Edit Profile" size="md" on:arc-close={() => open = false}>
<div style="display:flex; flex-direction:column; gap:12px;">
<label>Display Name<Input value="Ada Lovelace" /></label>
<label>Bio<Input value="Analytical engine enthusiast" /></label>
</div>
<div slot="footer">
<Button variant="ghost" onclick={() => open = false}>Cancel</Button>
<Button variant="primary" onclick={() => open = false}>Save Changes</Button>
</div>
</Modal> import { Component } from '@angular/core';
import { Button, Modal, Input } from '@arclux/arc-ui-angular';
@Component({
imports: [Button, Modal, Input],
template: `
<Button variant="secondary" (click)="open = true">Edit Profile</Button>
<Modal [open]="open" heading="Edit Profile" size="md" (arcClose)="open = false">
<div style="display:flex; flex-direction:column; gap:12px;">
<label>Display Name<Input value="Ada Lovelace" /></label>
<label>Bio<Input value="Analytical engine enthusiast" /></label>
</div>
<div slot="footer">
<Button variant="ghost" (click)="open = false">Cancel</Button>
<Button variant="primary" (click)="open = false">Save Changes</Button>
</div>
</Modal>
`,
})
export class EditProfileComponent {
open = false;
} import { createSignal } from 'solid-js';
import { Button, Modal, Input } from '@arclux/arc-ui-solid';
function EditProfileModal() {
const [open, setOpen] = createSignal(false);
return (
<>
<Button variant="secondary" onClick={() => setOpen(true)}>Edit Profile</Button>
<Modal open={open()} heading="Edit Profile" size="md" onArcClose={() => setOpen(false)}>
<div style="display:flex; flex-direction:column; gap:12px;">
<label>Display Name<Input value="Ada Lovelace" /></label>
<label>Bio<Input value="Analytical engine enthusiast" /></label>
</div>
<div slot="footer">
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Save Changes</Button>
</div>
</Modal>
</>
);
} import { useState } from 'preact/hooks';
import { Button, Modal, Input } from '@arclux/arc-ui-preact';
function EditProfileModal() {
const [open, setOpen] = useState(false);
return (
<>
<Button variant="secondary" onClick={() => setOpen(true)}>Edit Profile</Button>
<Modal open={open} heading="Edit Profile" size="md" onArcClose={() => setOpen(false)}>
<div style="display:flex; flex-direction:column; gap:12px;">
<label>Display Name<Input value="Ada Lovelace" /></label>
<label>Bio<Input value="Analytical engine enthusiast" /></label>
</div>
<div slot="footer">
<Button variant="ghost" onClick={() => setOpen(false)}>Cancel</Button>
<Button variant="primary" onClick={() => setOpen(false)}>Save Changes</Button>
</div>
</Modal>
</>
);
} API
| Prop | Type | Default | Description |
|---|---|---|---|
open | boolean | false | Controls the visible state of the dialog. Set to `true` to open the modal and activate the focus trap; set to `false` to close it, run the exit animation, and restore focus to the previously-focused element. |
heading | string | — | Text displayed in the modal header bar. Automatically linked to the dialog via `aria-labelledby` for screen-reader accessibility. Keep it short and action-oriented (e.g. "Delete Project" rather than "Are you sure?"). |
size | 'sm' | 'md' | 'lg' | 'md' | Controls the maximum width of the dialog panel. `sm` (400px) is ideal for simple confirmations, `md` (560px) for standard forms, and `lg` (720px) for content-heavy dialogs with tables or multi-column layouts. |
closable | boolean | true | When `true`, renders the built-in X close button and allows dismissal via Escape key and backdrop click. Set to `false` for critical decision modals where the user must explicitly choose an action from the footer buttons. |
fullscreen | boolean | false | Makes the modal fill the entire viewport. Useful for mobile forms or complex workflows. |
Events
| Event | Description |
|---|---|
arc-open | Fired when the modal opens |
arc-close | Fired when the modal closes |
See Also
- Drawer Slide-out panel with backdrop overlay, keyboard dismissal, and left/right positioning for off-canvas navigation, filters, and detail views.
- Sheet A sliding overlay panel that emerges from the bottom or right edge of the viewport, with a blurred backdrop, header, scrollable body, and footer slot.
- Dialog Small centered confirmation dialog wrapping arc-modal for simple confirm/cancel prompts — unsaved changes, session expiry, and discard decisions.