CwDialog

Built on the native <dialog> element: focus is trapped inside, Escape closes it, page scroll is locked while it's open, and focus returns to the trigger on close.

Confirmation

Last event: none yet

Untitled and long content

Without a title, pass ariaLabel; the close button stays on the right.

CwDialog example Copy code
let open = $state(false);

<CwButton onclick={() => (open = true)}>Delete device</CwButton>
<CwDialog bind:open title="Delete device?">
	<p>This removes the sensor and its history. This can't be undone.</p>
	{#snippet actions()}
		<CwButton variant="ghost" onclick={() => (open = false)}>Cancel</CwButton>
		<CwButton variant="danger" onclick={remove}>Delete</CwButton>
	{/snippet}
</CwDialog>

<!-- No visible title: give it an accessible name instead -->
<CwDialog bind:open={previewOpen} ariaLabel="Photo preview">…</CwDialog>

Delete device?

This removes "Greenhouse 7 sensor" and its reading history. This can't be undone.

Backdrop click disabled

Clicking outside does nothing. Close it with Escape or the button below.

All 24 devices synced successfully. This dialog has no visible title, so it is named with ariaLabel.

What's new in this release

  • Sensor cards now show a stale state when a device stops reporting.
  • Charts render a shared "No data available" overlay instead of fake zeros.
  • Form controls share one height scale: sm, md and lg.
  • The side navigation becomes an off-canvas drawer on phones.
  • Toasts pause while hovered or focused and never auto-dismiss danger messages.
  • Tables support compact density, numeric columns and a sticky header with maxHeight.
  • Dialogs lock page scroll and animate in and out.
  • Focus rings use one solid, high-contrast outline everywhere.
  • Status colours come from shared --cw-status-* tokens.
  • Light-theme borders and tone colours meet contrast targets.
  • The offline overlay re-checks the connection instead of reloading the page.
  • Calendars and date pickers are fully keyboard-operable.
API

Usage and API reference

CwDialog wraps a native `<dialog>` element with the behavior most apps want by default: open state binding, title, actions, escape handling, and backdrop close.

How to think about it

  1. Control visibility from the parent Use `bind:open` so the surrounding page decides when the dialog appears and disappears.
  2. Put body content in `children` and footer buttons in `actions` This keeps the markup easy to scan and matches the built-in dialog layout.
  3. Tighten close behavior only when needed Leave backdrop and escape close enabled unless the user must complete an explicit step.

Props and callbacks

APITypeDetails
open

Default: false

booleanDialog visibility state.
title
stringTitle shown in the dialog header.
closeOnBackdrop

Default: true

booleanAllows clicking the backdrop to close the dialog.
closeOnEscape

Default: true

booleanAllows the Escape key to close the dialog.
children
SnippetMain dialog content.
actions
SnippetFooter actions row.
onclose
() => voidRuns whenever the dialog closes itself.
ariaLabel
stringAccessible name when the dialog has no visible `title`.
closeLabel

Default: 'Close dialog'

stringAccessible label for the close button.
hideClose

Default: false

booleanHides the close button (keep another way to close).

Copy-paste examples

Each snippet is a complete, working example you can paste into a Svelte 5 component.

Confirmation dialog

The parent owns the `open` state and the action buttons close the dialog explicitly.

Confirmation dialog Copy code
<script lang="ts">
	let confirmOpen = $state(false);
</script>

<CwButton onclick={() => (confirmOpen = true)}>Delete zone</CwButton>

<CwDialog bind:open={confirmOpen} title="Delete zone">
	{#snippet children()}
		<p>This removes the zone and its alert rules.</p>
	{/snippet}
	{#snippet actions()}
		<CwButton variant="ghost" onclick={() => (confirmOpen = false)}>Cancel</CwButton>
		<CwButton variant="danger" onclick={() => (confirmOpen = false)}>Delete</CwButton>
	{/snippet}
</CwDialog>
Blocking dialog

Only use this pattern when the user must complete an intentional workflow.

Blocking dialog Copy code
<script lang="ts">
	let blockingOpen = $state(true);
	let closed = $state(false);
</script>

<CwDialog
	bind:open={blockingOpen}
	title="Required acknowledgement"
	closeOnBackdrop={false}
	closeOnEscape={false}
	onclose={() => (closed = true)}
>
	{#snippet children()}
		<p>Read the maintenance notice before continuing.</p>
	{/snippet}
	{#snippet actions()}
		<CwButton onclick={() => (blockingOpen = false)}>I understand</CwButton>
	{/snippet}
</CwDialog>