Apply user preferences to data attributes so your styles can follow them.

Preferences puts your preferences on data-* attributes and keeps them up to date. Use it around a part of your app, or apply a preference to the whole page.

Console
<script setup lang="ts">
import { createLocalStorage, createPreference } from "@ui-sdk/core"
import { Button } from "@ui-sdk/vue/button"
import { Preferences, usePreference } from "@ui-sdk/vue/preferences"
import { useLog } from "./log"

const view = createPreference({
  values: ["list", "grid"],
  defaultValue: "list",
  attribute: "data-view",
  storage: createLocalStorage("ui-sdk-preferences-view"),
})

const files = [
  { name: "Q3 roadmap.pdf", detail: "2.4 MB" },
  { name: "Launch checklist.md", detail: "12 KB" },
  { name: "Offsite photos", detail: "24 items" },
  { name: "Brand assets.zip", detail: "18 MB" },
]

const log = useLog()
const state = usePreference(view)

function choose(next: "list" | "grid", event: MouseEvent) {
  view.set(next, { reason: "press", event })
  log(`Saved the ${next} view`)
}
</script>

<template>
  <Preferences :preferences="[view]">
    <div data-slot="stack">
      <div data-slot="controls" role="group" aria-label="View">
        <Button
          :aria-pressed="state.value === 'list'"
          @click="choose('list', $event)"
          >List</Button
        >
        <Button
          :aria-pressed="state.value === 'grid'"
          @click="choose('grid', $event)"
          >Grid</Button
        >
      </div>
      <ul data-slot="files" aria-label="Files">
        <li v-for="file in files" :key="file.name">
          <span>{{ file.name }}</span>
          <span data-slot="hint">{{ file.detail }}</span>
        </li>
      </ul>
    </div>
  </Preferences>
</template>
Choose Grid, then reload the page. The files come back in the view you chose, and the console logs each change.

Installation

npm install @ui-sdk/vue @ui-sdk/core

Usage

Define the preference you want to apply. Here, view chooses how a file list is laid out:

preferences.ts
import { createPreference } from "@ui-sdk/core"

const view = createPreference({
  values: ["list", "grid"],
  defaultValue: "list",
  attribute: "data-view",
})

export { view }

Pass it to Preferences, then style the content by its attribute:

file-list.vue
<script setup lang="ts">
import { Preferences } from "@ui-sdk/vue/preferences"
import { view } from "./preferences"
</script>

<template>
  <Preferences :preferences="[view]">
    <ul data-slot="files">
      <li>Q3 roadmap.pdf</li>
      <li>Launch checklist.md</li>
    </ul>
  </Preferences>
</template>
[data-view="grid"] [data-slot="files"] {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
}

Preferences starts with data-view="list". Calling view.set("grid") updates it, and the list becomes a grid. Preferences renders with display: contents, so it adds no box of its own.

Save the Value

Add storage to save the choice between visits:

preferences.ts
import { createLocalStorage, createPreference } from "@ui-sdk/core"

const view = createPreference({
  values: ["list", "grid"],
  defaultValue: "list",
  attribute: "data-view",
  storage: createLocalStorage("ui-sdk-preferences-view"),
})

export { view }

The preference now saves changes in local storage and restores the saved value when the page reloads. You can also use session storage or cookies with createPreference.

Read the Value

Use usePreference when the value affects what your component renders:

view-switch.vue
<script setup lang="ts">
import { usePreference } from "@ui-sdk/vue/preferences"
import { view } from "./preferences"

const state = usePreference(view)
</script>

<template>
  <button
    :aria-pressed="state.value === 'grid'"
    @click="view.set(state.value === 'grid' ? 'list' : 'grid')"
  >
    Grid
  </button>
</template>

Pass the same preference instance wherever you need its value. usePreference also works outside a Preferences component.

Apply to the Whole Page

Set global: true when defining the preference:

const view = createPreference({
  values: ["list", "grid"],
  defaultValue: "list",
  attribute: "data-view",
  global: true,
  storage: createLocalStorage("ui-sdk-preferences-view"),
})

Keep passing it to Preferences. Its attribute now goes on <html>, so styles throughout the page can use it. A global preference needs only one mounted Preferences component to apply it.

Multiple Preferences

Pass more than one preference in the list. For example, you can apply view alongside a color scheme:

<Preferences :preferences="[view, colorScheme]">
  <App />
</Preferences>

Each preference uses its own attribute and global option. Wrappers that share a preference instance follow the same value. For independent choices, use separate preference instances and different storage keys.

Server Rendering

On the server, Preferences includes a script that applies saved values before the page paints. CSS can use those attributes immediately. usePreference starts with the server's value and updates after hydration.

Follow the server rendering setup to place Preferences in your app and keep cookie-backed preferences separate for each request.

If your Content Security Policy requires a nonce, pass the nonce from your response:

<Preferences :preferences="[view]" :nonce="nonce">
  <App />
</Preferences>

API Reference

Preferences

preferencesreadonly PreferenceLike[]
The preferences to show, from createPreference or createColorScheme.
Example: Usage
nonce?string
The nonce your Content Security Policy gives inline scripts.
Example: Server Rendering

usePreference

usePreference(preference) returns a computed ref containing the preference's current state. For a preference created with createPreference, the state is { value }. Read state.value.value in a script or state.value in a template, where Vue unwraps the ref. For a color scheme, the state also includes system and resolved.