Modal

Accessible focus-trapped dialog for critical actions and deep workflows.

Overview

The Modal component is a core building block of the Byteion design system, providing a robust, accessibility-compliant interface for its specific UI capabilities.

Why this exists

Modals interrupt the user to demand attention for critical actions or focused workflows. A well-built modal prevents background interaction, traps focus, and provides clear exit paths.

Mental model

A modal is a temporary overlay that blocks interaction with the page behind it. Use it when the task requires full attention — confirmations, forms, or detail views that should not navigate away.

When to use

• Confirmations for destructive actions (delete, cancel subscription) • Focused forms (sign in, quick edit) • Detail views that should not trigger a page navigation • Wizards and multi-step flows

When not to use

• Do not use modals for content the user needs to reference against the page — use a drawer or side panel • Avoid for non-critical notifications — use a toast • Do not use modals for navigation — use a page route • Avoid nesting modals — it creates focus and stacking nightmares

Basic example

The following example demonstrates standard usage, fully compliant with Byteion's token-first approach.

Variants

Each variant communicates a different meaning. Choose based on priority, not aesthetics.

Single default variant.

Sizes

Size should match the importance and context of the component.

  • sm — sm size.
  • md — md size.
  • lg — lg size.
  • xl — xl size.

States

This component reacts seamlessly to the following interaction and data states:

  • open — Component is expanded or visible.
  • closed — Component is collapsed or hidden.

Anatomy

• Overlay/Backdrop — dimmed background, clickable to dismiss • Dialog container — centered panel with max-width • Header — title and close button • Body — scrollable content area • Footer — action buttons (Cancel + Confirm)

Usage guidelines

  • Always provide a clear exit path — close button, Escape key, and backdrop click.
  • Trap focus inside the modal while open.
  • Scroll lock the body to prevent background scrolling.
  • Only one modal should be open at a time.
  • The Cancel button should be ghost/outline, Confirm should be primary or destructive.

Do and Don't

Do

  • Trap focus while open.
  • Provide close, Escape, and backdrop-click exits.
  • Scroll lock the body.
  • One modal at a time.

Don't

  • Do not nest modals.
  • Do not use for navigation.
  • Do not remove the Escape exit.
  • Do not open modals on page load.

Accessibility

Modal has role="dialog" and aria-modal="true". Title is linked via aria-labelledby. Focus is trapped and restored on close. Escape key dismisses.

Role:dialog

ARIA Attributes

  • aria-modal
  • aria-labelledby
  • aria-describedby
  • aria-hidden

Keyboard Navigation

  • Escape: Closes the modal (unless data-bi-modal-keyboard='false')
  • Tab / Shift+Tab: Cycles focus within the modal
  • Enter: Activates the focused control

Customization

Overlay uses --z-modal and backdrop blur. Dialog max-width and border-radius are customizable.

Compose with React cva()

Use the exportedcva() +cn() utilities to compose your own variant on top of the Byteion tokens.

import { cva, cn, type VariantProps } from '@byteion/react';

const myModal = cva('base classes…', {
 variants: {
  accent: { soft:'border-primary-200', loud:'border-primary-500' },
 },
 defaultVariants: { accent:'soft' },
});

type Props = React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof myModal>;

Compose with Vue cva()

Use the exportedcva() +cn() utilities inside standard Vue setup blocks.

<script setup lang="ts">
import { computed, useAttrs } from 'vue';
import { cva, type VariantProps } from '@byteion/vue';

interface Props { accent?:'soft' |'loud' }
const props = withDefaults(defineProps<Props>(), { accent:'soft' });
const attrs = useAttrs();

const myModal = cva('base classes…', {
  variants: { accent: { soft:'border-primary-200', loud:'border-primary-500' } },
});

const cls = computed(() => myModal({ accent: props.accent, class: attrs.class as string | undefined }));
</script>

Runtime events

Byteion keeps framework APIs idiomatic: HTML uses DOM custom events, React uses callback props, and Vue uses emitted events / v-model updates.

HTML / JS events

  • bi:modal:hide
  • bi:modal:show

React callbacks

  • onClose() => void

Vue emits

  • update:modelValue[v: boolean]
  • update:open[v: boolean]
  • close[]

Remote / AJAX

This component does not implement Byteion JS remote loading. Use normal HTML form submission, framework state, or your application data layer around it.

API Reference

Requires data-bi-modal="id" on the container. Toggle with data-bi-modal-toggle="id". Hide with data-bi-modal-hide.

HTML Data Attributes

  • data-bi-modal
  • data-bi-modal-content
  • data-bi-modal-toggle
  • data-bi-modal-hide
  • data-bi-backdrop

React Props · ModalProps

Extracted directly fromreact/src/components/Modal.tsx.

export interface ModalProps {
  /** Controlled open state. */
  open: boolean;
  /** Fired when the user dismisses via Escape, backdrop click, or close button. */
  onClose: () => void;
  /** Block backdrop click + Escape from closing. */
  staticBackdrop?: boolean;
  /** Disable Escape (keeps backdrop click). */
  keyboard?: boolean;
  /** Backdrop visual treatment. */
  backdrop?: 'clear' | 'soft';
  size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full';
  title?: ReactNode;
  description?: ReactNode;
  children?: ReactNode;
  footer?: ReactNode;
  className?: string;
  role?: string;
  componentName?: string;
  contentRef?: React.Ref<HTMLDivElement>;
}

Vue Props · ModalProps

Extracted directly fromvue/src/components/Modal.vue.

export interface ModalProps {
  /** v-model:open style. */
  modelValue?: boolean;
  /** Alias of modelValue — pass `open` if you don't use v-model. */
  open?: boolean;
  title?: string;
  description?: string;
  size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' | '5xl' | 'full';
  staticBackdrop?: boolean;
  keyboard?: boolean;
  backdrop?: 'clear' | 'soft';
  role?: string;
  componentName?: string;
}

Real-world examples

See how this component composes in common application contexts.

In a form

Developer tips

• Common mistake: opening a modal on page load (popups) — users hate this. • Edge case: on mobile, modals should slide up from the bottom (bottom sheet pattern). • Best practice: the modal title should describe the action, not the component ("Delete user" not"Confirmation dialog").

Common mistakes

Avoid hardcoded neutral colors, placeholder-only labels, duplicated primary actions, and missing keyboard states. Keep this component on Byteion tokens so dashboard, admin, POS, ERP, ecommerce back-office, Laravel, React, and Vue starter-kit screens can be rebranded without rewriting markup.

Production usage tips

1. Static HTML modals require @byteion/js for data-bi-modal behavior. React and Vue modals are self-contained — no JS runtime needed.

2. React and Vue modals should be controlled through state: open/onClose in React; v-model:open or :open + @close in Vue.

3. Test keyboard navigation with Tab, Shift+Tab, Escape, Enter, and Space. Verify focus does not escape to elements behind the backdrop.

4. Test focus restoration after close — focus should return to the trigger button, not to the top of the page.

5. Test screen reader announcements: modal should announce title, description, and role="dialog" on open.

6. Test body scroll lock on all viewport sizes. On iOS Safari, overflow: hidden may need position: fixed or touch-action: none as a companion.

7. Test dark mode and branded themes — the dialog uses bg-card and shadow-modal tokens that respond to theme changes.

Premium examples

Advanced patterns available in Byteion Pro:

• Bottom sheet modal for mobile • Fullscreen modal for image/document viewers • Confirm modal with checkbox ("Don't ask again")

Unlock Premium Patterns

Get access to copy-paste ready Pro blocks, SaaS templates, and advanced data-tables built with this component.

Explore Pro Components