---
name: settings
title: Settings
url: https://ui.frontic.com/components/sections/settings.md
tier: sections
install: npx @frontic/ui add settings
files:
  - components/sections/settings/Settings.vue
  - components/sections/settings/index.ts
  - components/sections/settings/types.ts
registryDependencies:
  - button
  - card
  - field
  - separator
  - switch
---

# Settings

Settings card: switches in titled groups, each with a hint, disabled where an option is not available, and reset and save actions.

## Install

```sh
npx @frontic/ui add settings
```

Built from: [Button](https://ui.frontic.com/components/ui/button.md), [Card](https://ui.frontic.com/components/ui/card.md), [Field](https://ui.frontic.com/components/ui/field.md), [Separator](https://ui.frontic.com/components/ui/separator.md), [Switch](https://ui.frontic.com/components/ui/switch.md)

## Examples

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { Settings, type SettingsGroup } from '@/components/sections/settings'

const groups: SettingsGroup[] = [
  {
    label: 'Email',
    options: [
      { id: 'orders', label: 'Order updates', description: 'Confirmation, dispatch and delivery.' },
      { id: 'restock', label: 'Restock alerts', description: 'When a size on your wishlist is back.' },
      { id: 'newsletter', label: 'Basecamp Store newsletter', description: 'New arrivals and offers. 10% off your first order.' },
    ],
  },
  {
    label: 'Text message',
    options: [{ id: 'sms', label: 'Delivery day SMS', description: 'Add a mobile number to turn this on.', disabled: true }],
  },
]

const enabled = ref({ orders: true, restock: true, newsletter: false, sms: false })
</script>

<template>
  <div class="mx-auto w-full max-w-md">
    <Settings v-model="enabled" title="Notifications" description="Choose what we send you, and where." save-label="Save preferences" :groups="groups" />
  </div>
</template>
```

## API reference

### Settings

Lets the shopper turn grouped options on or off, then save or reset the changes.

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `groups` (required) | `SettingsGroup[]` |  | The options to switch on and off, in titled groups. |
| `title` | `string` | `'Settings'` | Shown as the card's title. |
| `description` | `string` |  | Shown under the title. |
| `resetLabel` | `string` | `'Reset'` | Shown on the button that undoes the changes since the last save. |
| `saveLabel` | `string` | `'Save changes'` | Shown on the button that saves the options. |
| `surface` | `'raised' \| 'bare'` | `'raised'` | Bare drops the edge, shadow and inset, for a section placed inside another Card. |
| `class` | `ClassValue` |  | Classes added to the part's own element, after its recipe classes. |
| `modelValue` | `Record<string, boolean>` | `{}` | Which options are on, by option id. |

| Event | Payload | Description |
| --- | --- | --- |
| `update:modelValue` | `value: Record<string, boolean>` | Fires with the new value when `modelValue` changes; `v-model` binds it. |
| `save` | `enabled: Record<string, boolean>` | Fires with the on/off state of every option when the customer saves the settings. |

| Data attribute | Values | Description |
| --- | --- | --- |
| `data-surface` | `'raised' \| 'bare'` | Carries the `surface` prop. |

## Exports

```ts
import { type SettingsGroup, type SettingsOption } from '@/components/sections/settings'
```

- `SettingsGroup`: below
- `SettingsOption`: below

```ts
interface SettingsGroup {
  label: string
  options: SettingsOption[]
}

interface SettingsOption {
  /** Key of the option in the model. */
  id: string
  label: string
  description?: string
  disabled?: boolean
}
```

## Source

`components/sections/settings/Settings.vue`

```vue
<script lang="ts">
/** Lets the shopper turn grouped options on or off, then save or reset the changes. */
export default {}
</script>

<script setup lang="ts">
import type { HTMLAttributes } from 'vue'
import type { SettingsGroup } from './types'
import { ref, useId } from 'vue'
import { Button } from '@library/components/ui/button'
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@library/components/ui/card'
import { Field, FieldContent, FieldDescription, FieldLabel, FieldLegend, FieldSet } from '@library/components/ui/field'
import { Separator } from '@library/components/ui/separator'
import { Switch } from '@library/components/ui/switch'

const props = withDefaults(
  defineProps<{
    /** The options to switch on and off, in titled groups. */
    groups: SettingsGroup[]
    /** Shown as the card's title. */
    title?: string
    /** Shown under the title. */
    description?: string
    /** Shown on the button that undoes the changes since the last save. */
    resetLabel?: string
    /** Shown on the button that saves the options. */
    saveLabel?: string
    /** Bare drops the edge, shadow and inset, for a section placed inside another Card. */
    surface?: 'raised' | 'bare'
    class?: HTMLAttributes['class']
  }>(),
  {
    surface: 'raised',
    title: 'Settings',
    resetLabel: 'Reset',
    saveLabel: 'Save changes',
  },
)
const emit = defineEmits<{
  /** Fires with the on/off state of every option when the customer saves the settings. */
  save: [enabled: Record<string, boolean>]
}>()

/** Which options are on, by option id. */
const enabled = defineModel<Record<string, boolean>>({ default: () => ({}) })

const id = useId()
const saved = ref({ ...enabled.value })

function toggle(option: string, on: boolean) {
  enabled.value = { ...enabled.value, [option]: on }
}

function reset() {
  enabled.value = { ...saved.value }
}

function save() {
  saved.value = { ...enabled.value }
  emit('save', saved.value)
}
</script>

<template>
  <Card :surface="surface" :class="props.class">
    <CardHeader>
      <CardTitle>{{ title }}</CardTitle>
      <CardDescription v-if="description">{{ description }}</CardDescription>
    </CardHeader>
    <CardContent>
      <div class="flex flex-col gap-6">
        <template v-for="(group, index) in groups" :key="group.label">
          <Separator v-if="index > 0" />
          <FieldSet>
            <FieldLegend variant="label">{{ group.label }}</FieldLegend>
            <Field v-for="option in group.options" :key="option.id" orientation="horizontal" :data-disabled="option.disabled || undefined">
              <FieldContent>
                <FieldLabel :for="`${id}-${option.id}`">{{ option.label }}</FieldLabel>
                <FieldDescription v-if="option.description">{{ option.description }}</FieldDescription>
              </FieldContent>
              <Switch :id="`${id}-${option.id}`" :model-value="!!enabled[option.id]" :disabled="option.disabled" @update:model-value="toggle(option.id, $event)" />
            </Field>
          </FieldSet>
        </template>
      </div>
    </CardContent>
    <CardFooter class="justify-end gap-2">
      <Button variant="ghost" color="neutral" @click="reset">{{ resetLabel }}</Button>
      <Button @click="save">{{ saveLabel }}</Button>
    </CardFooter>
  </Card>
</template>
```

`components/sections/settings/types.ts`

```ts
export interface SettingsOption {
  /** Key of the option in the model. */
  id: string
  label: string
  description?: string
  disabled?: boolean
}

/** A titled set of options, such as one channel of notification settings. */
export interface SettingsGroup {
  label: string
  options: SettingsOption[]
}
```
