Skip to content
ARC UI ARC Reactive Components
Docs Components Tokens Synthesizer
v2.1.0
Getting Started Frameworks Design Tokens Theming Theme Synthesizer Accessibility Browser Support Changelog Contributing All Components App ShellAspect GridAuth ShellCenterClusterContainerDashboard GridDockFloat BarInsetMasonryPage HeaderPage LayoutResizableResponsive SwitcherSectionSettings LayoutSplit PaneStatus BarStickyToolbar Anchor NavBottom NavBreadcrumbBreadcrumb MenuCommand BarDrawerFooterLinkNavigation MenuPage IndicatorPaginationRailScroll IndicatorScroll SpyScroll To TopSidebarSkip LinkSpeed DialStepper NavTabsTop BarTree View AccordionAspect RatioAvatarAvatar GroupCalloutCardCarouselCollapsibleColor SwatchCTA BannerDividerEmpty StateFeature CardIconImageInfinite ScrollMarqueeScroll AreaSeparatorSkeletonSpinnerStackVirtual List Animated NumberBadgeComparisonCountdown TimerData TableDescription ListDiffKey ValueListMeterSparklineStatStepperTableTagTimelineValue Card BlockquoteCode BlockGradient TextHighlightKbdMarkdownNumber FormatProseTextTime AgoTruncateTypewriter ButtonButton GroupCalendarCheckboxChipColor PickerComboboxCopy ButtonDate PickerFieldsetFile UploadFormHotkeyIcon ButtonInputInput GroupLabelMulti SelectNumber InputOTP InputPin InputRadio GroupRange SliderRatingSearchSegmented ControlSelectSliderSortable ListSwitch GroupTextareaTheme ToggleTime PickerToggle AlertAnnouncementBannerCommand PaletteConfirmConnection StatusContext MenuDialogDropdown MenuGuided TourHover CardInline MessageLoading OverlayModalNotification PanelPopoverProgressProgress ToastSheetSnackbarSpotlightToastTooltip
Components Modal
feedback interactive
<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

Edit Profile
Cancel Save Changes

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