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.

html
<UiSettingsModal :navigation="..." />

Navigation

required

navigation is the sidebar's sections, passed straight through to Navigation — so type: 'label' headings and nested children work exactly as they do there. Each section renders through a slot named after its value.

Sections are matched by value, not path — a path would turn the sidebar row into a real link and navigate away from the modal. Everything else about navigation is plain Navigation: type: 'label' headings, type: 'separator' rules, icon, badge and nested children all behave identically.

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.

general

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.

Open: false

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.

Close button

closeButton shows the × in the panel's header, and is on by default. It's independent of dismissible: dropping the button while leaving Escape and the backdrop alone still lets people out, and dropping both is what makes a dialog closable only through your own control.

Section `heading` and `description` — the panel's own copy, independent of the sidebar `title`

heading and description on a section are SettingsModal's own additions to the Navigation item shape — they fill the panel's header, and Navigation ignores them. heading defaults to the section's title.

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.

`sidebar-header` slot — replaces the `title` label above the navigation

`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.

`:close-button="false"` and `:dismissible="false"` — the modal closes only through your own control


API Reference

Generated from the component's source — props, slots and emits as the component actually declares them.

Props

Prop
Type
Default
Description
navigationrequired
SettingsSection[]
The sections listed in the sidebar, passed straight through to `Navigation` — so `type: 'label'` headings, `type: 'separator'` rules and nested `children` all work the same way here.
string
The `value` of the open section — bind with `v-model` to control it, or leave it off to let the modal track its own (starting at `defaultValue`).
string
the first section with a `value`
Which section is open first when uncontrolled.
boolean
Whether the modal is open, for controlled usage with v-model:open.
boolean
false
Whether the modal is open by default when uncontrolled.
string
'Settings'
The label above the sidebar. Pass an empty string to drop it.
NavigationColor
'neutral'
The color of the selected section in the sidebar.
NavigationVariant
'ghost'
The visual style of the sidebar's rows.
"md" | "lg" | "xl"
'xl'
The width of the panel. Smaller than `Modal`'s range, since the sidebar needs the room.
boolean
false
Grows the panel to fill the viewport instead of sizing it from `size`, the way Discord's settings do.
boolean
true
Whether clicking the backdrop or pressing Escape closes the modal.
boolean
true
Shows the close button in the panel's header.

Slots

Slot
sidebar-header
sidebar-footer
header
activeSection?.value ?? 'default'
default
footer

Emits

Event
Payload
Description
update:open
[value: boolean]
update:modelValue
[value: string]
select
[section: SettingsSection]

Types

ts
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;
}
ts
type NavigationColor = "neutral" | "primary" | "secondary" | "success" | "error" | "warning" | "info" | "pending";
ts
interface NavigationItem {
    title: string;
    path?: string;
    value?: string;
    icon?: string;
    badge?: string | number;
    children?: NavigationItem[];
    defaultOpen?: boolean;
    /**
     * @defaultValue 'link'
     */
    type?: "link" | "label" | "separator";
}
ts
type NavigationVariant = "solid" | "subtle" | "soft" | "ghost";