Overlays
SettingsModal
A two-pane settings dialog — a `Navigation` sidebar of sections on the left, the selected section's panel on the right — in the style of Discord's or Claude's settings.
<UiSettingsModal :navigation="..." />Model value
modelValue is the value of the section currently showing — bind it with v-model and the open section becomes readable and writable from outside the dialog, which is how a “Manage billing” button elsewhere can open settings on the right panel. Note there are two independent models here: this one for the section, and v-model:open for the dialog itself.
Default value
defaultValue is the section that opens first when you don't bind v-model.
Open
open is whether the dialog itself is showing — separate from modelValue, which is the section inside it. Bind it with v-model:open so the close button and the backdrop can shut the dialog on their own; pass it one way only when you're taking over closing entirely.
There are two independent models: v-model:open for the dialog and v-model for the open section. Leave either off to let the component track it internally.
Default open
defaultOpen opens the dialog as soon as it mounts, with no state of your own. Gate the mount rather than the flag — a settings dialog that appears on every render is a page nobody can use — which is what the v-if below does.
Title
title is the label above the sidebar, “Settings” by default. Name the thing being configured when the dialog isn't about the reader — “Workspace”, “Project” — and pass an empty string to drop the label entirely, which the sidebar-header slot also does by replacing it.
Color
color is the accent on the selected sidebar row, passed straight through to the inner Navigation. Neutral is the default and usually right — settings are a place people visit deliberately, so the sidebar doesn't need to compete with the panel.
Variant
variant is how the sidebar's rows are drawn — ghost by default, so only the selected row carries any fill. subtle, soft and solid give the selection progressively more weight, which helps when the sidebar is long enough that the reader loses track of where they are.
Size
size is the panel's width — md, lg or xl, the default. The range starts higher than Modal's because the sidebar needs its own room; the sidebar keeps its width at every size, so the panel takes the difference.
Fullscreen
fullscreen grows the panel to fill the viewport instead of sizing it from size, the way Discord's settings do.
The panel is a fixed 34rem tall (capped by the viewport) so switching sections doesn't resize the dialog under the cursor — use fullscreen for the Discord treatment, which takes the whole viewport instead.
Dismissible
dismissible is whether Escape and a click on the backdrop close the dialog, and it's on by default. Turn it off while there are unsaved changes — a settings dialog is exactly where a stray Escape costs someone their work — and give them a deliberate way out in the footer slot instead.
A complete settings dialog
Everything the sidebar understands at once — type: 'label' group headings, badge counts, nested children, and a default slot catching the sections that have no panel of their own yet.
`header` slot — replaces the panel's heading and description, and is handed the active `section`
Each section renders through a slot named after its value (#profile, #billing, …); anything without one falls through to the default slot, which receives the active section object so a single slot can serve several sections.
API Reference
Generated from the component's source — props, slots and emits as the component actually declares them.
Props
navigationrequiredSettingsSection[]string"md" | "lg" | "xl"'xl'booleanfalseSlots
sidebar-headersidebar-footerheaderactiveSection?.value ?? 'default'defaultfooterEmits
update:open[value: boolean]update:modelValue[value: string]select[section: SettingsSection]Types
interface SettingsSection {
title: string;
value?: string;
icon?: string;
badge?: string | number;
children?: SettingsSection[];
defaultOpen?: boolean;
/**
* @defaultValue 'link'
*/
type?: "link" | "label" | "separator";
/**
* The panel's heading. Defaults to `title`.
*/
heading?: string;
/**
* A line of explanation shown under the panel's heading.
*/
description?: string;
}type NavigationColor = "neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending";interface NavigationItem {
title: string;
path?: string;
value?: string;
icon?: string;
badge?: string | number;
children?: NavigationItem[];
defaultOpen?: boolean;
/**
* @defaultValue 'link'
*/
type?: "link" | "label" | "separator";
}type NavigationVariant = "solid" | "subtle" | "soft" | "ghost";