# Appearance pickers

> Pickers for how an app looks and reads. A gallery of named themes with mini previews, reading settings for text size, line width and spacing with a live sample, and a wallpaper picker with upload and dimming. They hold the choice; you apply and save it.

Source: https://docs.nasaqui.com/components/appearance-pickers

## Install

```bash
npx shadcn@latest add https://docs.nasaqui.com/r/appearance-pickers.json
```

Three pickers for the "Appearance" page of a settings screen.

| Control | What it is |
| --- | --- |
| `ThemeGallery` | Cards, one per named theme, each with a small preview of its page, sidebar, text and accent. One is chosen at a time. |
| `ReadingSettings` | Text size, line width and line spacing as segmented controls, a reset button and a live sample paragraph. |
| `WallpaperPicker` | A grid of gradients, colours and pictures grouped by section, an optional upload tile and a dim slider. |

They only hold the choice and report it. Applying it (setting a class, a CSS variable, a background) and saving it is your code, so the same pickers work in a web app, a desktop shell and an extension.

## When to use

- A product with several named themes beyond light and dark.
- Reading-heavy products (articles, documents, notes) where comfort settings matter.
- Desktop or dashboard shells with a wallpaper.

## When not to use

- Just light, dark and system: use [`ThemeToggle`](https://docs.nasaqui.com/components/theme-toggle).
- Picking one colour value: use [`ColorPicker`](https://docs.nasaqui.com/components/color-picker).
- Language and region: use the language switcher.

## Import

```tsx
import { ThemeGallery, ReadingSettings, WallpaperPicker, readingStyleVars, wallpaperCss, parseReadingPreferences } from "@fadymondy/nasaq/web";
```

## Quick start

```tsx
import { ReadingSettings, ThemeGallery, readingStyleVars, type ReadingPreferences } from "@fadymondy/nasaq/web";
import { useState } from "react";

const themes = [
  { id: "paper", label: "Paper", mode: "light" as const, swatches: ["var(--nq-surface)", "var(--nq-surface-soft)", "var(--nq-fg)", "var(--nq-brand)"] },
  { id: "ink", label: "Ink", mode: "dark" as const, swatches: ["var(--nq-fg)", "var(--nq-fg-muted)", "var(--nq-surface)", "var(--nq-brand)"] },
];

export function Appearance() {
  const [theme, setTheme] = useState("paper");
  const [reading, setReading] = useState<ReadingPreferences>({ fontSize: "md", width: "normal", spacing: "normal" });
  return (
    <div className="flex flex-col gap-8">
      <ThemeGallery themes={themes} value={theme} onValueChange={setTheme} />
      <ReadingSettings value={reading} onValueChange={setReading} />
      <article style={readingStyleVars(reading)}>{/* uses var(--reading-scale) etc. */}</article>
    </div>
  );
}
```

## Anatomy

```
ThemeGallery      data-slot="theme-gallery"      heading + radio group of theme-card (preview, mode icon, name, description, check)
ReadingSettings   data-slot="reading-settings"   rows of ToggleGroups, Reset, then reading-preview (live sample)
WallpaperPicker   data-slot="wallpaper-picker"   sections of wallpaper-none / wallpaper-tile radios, upload tile, dim Slider
```

## API

### ThemeGallery

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `themes` | `readonly AppearanceTheme[]` | none | `{ id, label, description?, mode?, swatches }`. `swatches` are up to four CSS colours: page, surface, text, accent. Prefer token colours. |
| `value` / `defaultValue` | `string` | first theme | Selected theme id. |
| `onValueChange` | `(id: string) => void` | none | Choice changed. |
| `label` | `ReactNode \| false` | "Theme" | Heading; `false` hides it (then pass `aria-label`). |
| `columns` | `2 \| 3 \| 4` | `4` | Cards per row at the widest (two on phones). |
| `disabled` / `name` | | | Usual radio group props. |
| `labels` | `AppearanceLabels` | en / ar | Text overrides. |

### ReadingSettings

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `value` / `defaultValue` | `ReadingPreferences` | `{ fontSize: "md", width: "normal", spacing: "normal" }` | `fontSize` sm / md / lg / xl, `width` narrow / normal / wide / full, `spacing` compact / normal / relaxed. |
| `onValueChange` | `(next: ReadingPreferences) => void` | none | Any part changed. |
| `controls` | `("fontSize" \| "width" \| "spacing")[]` | all | Which rows to show. |
| `showPreview` | `boolean` | `true` | Live sample under the controls. |
| `previewTitle` / `previewBody` | `ReactNode` | built in | Replace the sample text. |
| `disabled` | `boolean` | `false` | Disable all controls. |
| `labels` | `AppearanceLabels` | en / ar | Text overrides, including `sizes`, `widths`, `spacings`. |

### WallpaperPicker

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `wallpapers` | `readonly AppearanceWallpaper[]` | none | `{ id, label, background, group? }`. `background` is a CSS background or a picture URL. |
| `value` / `defaultValue` | `string \| null` | `null` | Selected id; `null` is none. |
| `onValueChange` | `(id: string \| null) => void` | none | Choice changed. |
| `dim` / `onDimChange` | `number` / `(percent) => void` | none | Dim amount 0 to 60. Passing either shows the slider. |
| `onUpload` | `(file: File) => void \| Promise<void>` | none | Shows the upload tile. Add the new wallpaper to `wallpapers` yourself. |
| `accept` | `string` | `"image/*"` | File types for the upload. |
| `allowNone` | `boolean` | `true` | A "None" tile first. |
| `label` | `ReactNode \| false` | "Wallpaper" | Heading. |

### Helpers (pure, no React)

| Function | Description |
| --- | --- |
| `readingStyleVars(prefs)` | `{ "--reading-scale", "--reading-max-width", "--reading-line-height" }` for the reading area's `style`. |
| `parseReadingPreferences(saved)` | Saved JSON or an object to valid preferences; unknown or missing fields use the defaults. |
| `stepReadingFontSize(size, delta)` / `readingFontPercent(size)` | One size up or down; "113" for large. |
| `wallpaperCss(background)` | The CSS `background` shorthand; pictures get `center / cover no-repeat url(...)`. |
| `groupWallpapers`, `clampWallpaperDim`, `themePreviewColors`, `isDefaultReading` | Small pure helpers. |

## Examples

Wallpaper with dimming and upload:

```tsx
import { WallpaperPicker, wallpaperCss } from "@fadymondy/nasaq/web";
import { useState } from "react";

export function Desk({ wallpapers }: { wallpapers: React.ComponentProps<typeof WallpaperPicker>["wallpapers"] }) {
  const [id, setId] = useState<string | null>(null);
  const [dim, setDim] = useState(20);
  const current = wallpapers.find((w) => w.id === id);
  return (
    <div style={{ background: current ? wallpaperCss(current.background) : undefined }}>
      <WallpaperPicker wallpapers={wallpapers} value={id} onValueChange={setId} dim={dim} onDimChange={setDim} onUpload={async (file) => { /* upload, then add to wallpapers */ }} />
    </div>
  );
}
```

## Accessibility

- `ThemeGallery` and `WallpaperPicker` are radio groups: Tab enters, arrow keys move (following the reading direction), Space selects. The group is named by its heading.
- Wallpaper tiles have no visible text, so each has its `label` as its accessible name and tooltip. Use a real label, not "Wallpaper 3".
- Reading settings buttons have text or `aria-label`s ("Larger text"); the size steppers stop being available at the ends.
- The selected card shows a check as well as a border, so selection never relies on colour alone.

## RTL & i18n

- Text is English or Arabic through `labels`; pass localised theme and wallpaper names.
- The percent labels (`113%`) are isolated left-to-right; the grids and toggle groups follow the reading direction; the reset icon mirrors.
- The reading sample follows the page direction, so line width in `ch` works for Arabic too.

## Styling & tokens

Cards use `border-border`, `bg-card`, `bg-nq-selected` and `border-primary` when chosen. Theme previews take their colours from the theme's own swatches, so give token colours (`var(--nq-brand)`) rather than hex.
Target `[data-slot="theme-card"]`, `[data-slot="wallpaper-tile"]`, `[data-slot="reading-preview"]`.

## Do / Don't

- Do apply the choice at once; the preview is only a hint.
- Do keep wallpapers readable behind text: offer the dim slider.
- Don't offer more than about eight themes; a gallery is not a catalogue.
- Don't rely on `swatches` alone for meaning; every theme has a name.

## Related

- [`ThemeToggle`](https://docs.nasaqui.com/components/theme-toggle)
- [`ColorPicker`](https://docs.nasaqui.com/components/color-picker)
- [`ToggleGroup`](https://docs.nasaqui.com/components/toggle-group)
- [`Slider`](https://docs.nasaqui.com/components/slider)

## Lab

https://docs.nasaqui.com/?path=/docs/components-pickers-appearance-pickers--docs

## Code

### React

```tsx
import { ReadingSettings, ThemeGallery, readingStyleVars, type ReadingPreferences } from "@fadymondy/nasaq/web";
import { useState } from "react";

const themes = [
  { id: "paper", label: "Paper", mode: "light" as const, swatches: ["var(--nq-surface)", "var(--nq-surface-soft)", "var(--nq-fg)", "var(--nq-brand)"] },
  { id: "ink", label: "Ink", mode: "dark" as const, swatches: ["var(--nq-fg)", "var(--nq-fg-muted)", "var(--nq-surface)", "var(--nq-brand)"] },
];

export function Appearance() {
  const [theme, setTheme] = useState("paper");
  const [reading, setReading] = useState<ReadingPreferences>({ fontSize: "md", width: "normal", spacing: "normal" });
  return (
    <div className="flex flex-col gap-8">
      <ThemeGallery themes={themes} value={theme} onValueChange={setTheme} />
      <ReadingSettings value={reading} onValueChange={setReading} />
      <article style={readingStyleVars(reading)}>{/* uses var(--reading-scale) etc. */}</article>
    </div>
  );
}
```

### shadcn

```tsx
import { ReadingSettings, ThemeGallery, readingStyleVars, type ReadingPreferences } from "@/components/ui/appearance-pickers";
import { useState } from "react";

const themes = [
  { id: "paper", label: "Paper", mode: "light" as const, swatches: ["var(--nq-surface)", "var(--nq-surface-soft)", "var(--nq-fg)", "var(--nq-brand)"] },
  { id: "ink", label: "Ink", mode: "dark" as const, swatches: ["var(--nq-fg)", "var(--nq-fg-muted)", "var(--nq-surface)", "var(--nq-brand)"] },
];

export function Appearance() {
  const [theme, setTheme] = useState("paper");
  const [reading, setReading] = useState<ReadingPreferences>({ fontSize: "md", width: "normal", spacing: "normal" });
  return (
    <div className="flex flex-col gap-8">
      <ThemeGallery themes={themes} value={theme} onValueChange={setTheme} />
      <ReadingSettings value={reading} onValueChange={setReading} />
      <article style={readingStyleVars(reading)}>{/* uses var(--reading-scale) etc. */}</article>
    </div>
  );
}
```

### Vue

```vue
<script setup lang="ts">
import { NqReadingSettings, NqThemeGallery, readingStyleVars, type ReadingPreferences } from "@fadymondy/nasaq/vue";
import { computed, ref } from "vue";

const themes = [
  { id: "paper", label: "Paper", mode: "light" as const, swatches: ["var(--nq-surface)", "var(--nq-surface-soft)", "var(--nq-fg)", "var(--nq-brand)"] },
  { id: "ink", label: "Ink", mode: "dark" as const, swatches: ["var(--nq-fg)", "var(--nq-fg-muted)", "var(--nq-surface)", "var(--nq-brand)"] },
];

const theme = ref("paper");
const reading = ref<ReadingPreferences>({ fontSize: "md", width: "normal", spacing: "normal" });
const vars = computed(() => readingStyleVars(reading.value));
</script>

<template>
  <div class="flex flex-col gap-8">
    <NqThemeGallery v-model="theme" :themes="themes" />
    <NqReadingSettings v-model="reading" />
    <article :style="vars"><!-- uses var(--reading-scale) etc. --></article>
  </div>
</template>
```

### Blade

```blade
<x-nq::appearance-pickers class="max-w-2xl">
    <x-nq::appearance-pickers.theme-gallery
        default-value="paper"
        :themes="[
            ['id' => 'paper', 'label' => 'Paper', 'description' => 'Warm and bright', 'mode' => 'light', 'swatches' => ['#fbf8f3', '#efe9df', '#1f1b16', '#0a7a5a']],
            ['id' => 'ink', 'label' => 'Ink', 'description' => 'Easy on the eyes', 'mode' => 'dark', 'swatches' => ['#14110f', '#1f1b18', '#f1ebe3', '#3ecf9d']],
        ]"
    />
    <x-nq::appearance-pickers.reading-settings :default-value="['fontSize' => 'md', 'width' => 'normal', 'spacing' => 'normal']" />
    <x-nq::appearance-pickers.wallpaper-picker
        default-value="dawn"
        :dim="20"
        upload
        :wallpapers="[
            ['id' => 'dawn', 'label' => 'Dawn', 'background' => 'linear-gradient(135deg, #f6d365, #fda085)', 'group' => 'Gradients'],
            ['id' => 'sea', 'label' => 'Sea', 'background' => 'linear-gradient(135deg, #43cea2, #185a9d)', 'group' => 'Gradients'],
            ['id' => 'city', 'label' => 'City', 'background' => '/wallpapers/city.jpg', 'group' => 'Photos'],
        ]"
    />
</x-nq::appearance-pickers>
```

### HTML + Alpine

```html
<div data-slot="appearance-pickers" class="flex min-w-0 flex-col gap-6 max-w-2xl"><div data-slot="theme-gallery" class="flex min-w-0 flex-col gap-2">
    <div id="nq-theme-5376f7dd" class="text-label text-foreground">Theme</div>    <div role="radiogroup" x-data="nqRadioGroup('paper')" x-modelable="value" x-bind="root"
         aria-labelledby="nq-theme-5376f7dd"                 class="grid gap-3 grid-cols-2 sm:grid-cols-4">
                                <button type="button" role="radio" data-slot="theme-card" x-bind="radio('paper')" aria-checked="true" tabindex="0"
                 data-checked                                 class="group relative flex min-w-0 cursor-pointer flex-col gap-2 rounded-card border border-border bg-card p-2 text-start outline-none transition-colors hover:border-nq-line-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-checked:border-primary data-checked:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
                <span aria-hidden="true" class="relative flex h-20 overflow-hidden rounded-control border border-border" style="background: #fbf8f3">
                    <span class="flex w-1/3 flex-col gap-1 p-1.5" style="background: #efe9df">
                        <span class="h-1.5 w-full rounded-full opacity-80" style="background: #0a7a5a"></span>
                        <span class="h-1 w-3/4 rounded-full opacity-40" style="background: #1f1b16"></span>
                        <span class="h-1 w-1/2 rounded-full opacity-40" style="background: #1f1b16"></span>
                    </span>
                    <span class="flex flex-1 flex-col gap-1.5 p-2">
                        <span class="h-2 w-2/3 rounded-full opacity-70" style="background: #1f1b16"></span>
                        <span class="h-1.5 w-full rounded-full opacity-30" style="background: #1f1b16"></span>
                        <span class="h-1.5 w-5/6 rounded-full opacity-30" style="background: #1f1b16"></span>
                        <span class="mt-auto h-3.5 w-1/3 rounded-control" style="background: #0a7a5a"></span>
                    </span>
                </span>
                <span class="flex min-w-0 items-center gap-1.5 px-0.5 pb-0.5">
                    <svg aria-hidden="true" class="size-3.5 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <circle cx="12" cy="12" r="4"/>
  <path d="M12 2v2"/>
  <path d="M12 20v2"/>
  <path d="m4.93 4.93 1.41 1.41"/>
  <path d="m17.66 17.66 1.41 1.41"/>
  <path d="M2 12h2"/>
  <path d="M20 12h2"/>
  <path d="m6.34 17.66-1.41 1.41"/>
  <path d="m19.07 4.93-1.41 1.41"/>
</svg>                    <span class="min-w-0 flex-1">
                        <span class="block truncate text-label text-foreground">Paper</span>
                        <span class="block truncate text-caption text-muted-foreground">Warm and bright</span>                    </span>
                    <span data-slot="radio-indicator" x-show="isChecked('paper')"  class="grid size-4 shrink-0 place-items-center rounded-full bg-primary text-primary-foreground">
                        <svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>                    </span>
                </span>
            </button>
                                <button type="button" role="radio" data-slot="theme-card" x-bind="radio('ink')" aria-checked="false" tabindex="-1"
                 data-unchecked                                 class="group relative flex min-w-0 cursor-pointer flex-col gap-2 rounded-card border border-border bg-card p-2 text-start outline-none transition-colors hover:border-nq-line-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-checked:border-primary data-checked:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50">
                <span aria-hidden="true" class="relative flex h-20 overflow-hidden rounded-control border border-border" style="background: #14110f">
                    <span class="flex w-1/3 flex-col gap-1 p-1.5" style="background: #1f1b18">
                        <span class="h-1.5 w-full rounded-full opacity-80" style="background: #3ecf9d"></span>
                        <span class="h-1 w-3/4 rounded-full opacity-40" style="background: #f1ebe3"></span>
                        <span class="h-1 w-1/2 rounded-full opacity-40" style="background: #f1ebe3"></span>
                    </span>
                    <span class="flex flex-1 flex-col gap-1.5 p-2">
                        <span class="h-2 w-2/3 rounded-full opacity-70" style="background: #f1ebe3"></span>
                        <span class="h-1.5 w-full rounded-full opacity-30" style="background: #f1ebe3"></span>
                        <span class="h-1.5 w-5/6 rounded-full opacity-30" style="background: #f1ebe3"></span>
                        <span class="mt-auto h-3.5 w-1/3 rounded-control" style="background: #3ecf9d"></span>
                    </span>
                </span>
                <span class="flex min-w-0 items-center gap-1.5 px-0.5 pb-0.5">
                    <svg aria-hidden="true" class="size-3.5 shrink-0 text-muted-foreground" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/>
</svg>                                        <span class="min-w-0 flex-1">
                        <span class="block truncate text-label text-foreground">Ink</span>
                        <span class="block truncate text-caption text-muted-foreground">Easy on the eyes</span>                    </span>
                    <span data-slot="radio-indicator" x-show="isChecked('ink')"  style="display: none"  class="grid size-4 shrink-0 place-items-center rounded-full bg-primary text-primary-foreground">
                        <svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg>                    </span>
                </span>
            </button>
                    </div>
</div>
    <div data-slot="reading-settings" x-data="nqReadingSettings(JSON.parse('{\u0022fontSize\u0022:\u0022md\u0022,\u0022width\u0022:\u0022normal\u0022,\u0022spacing\u0022:\u0022normal\u0022}'), false)" x-modelable="prefs" class="flex min-w-0 flex-col gap-4">
            <div class="flex min-w-0 flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-4">
            <div class="text-label text-foreground sm:w-32 sm:shrink-0">Text size</div>
            <div class="min-w-0">
                <div class="flex flex-wrap items-center gap-2">
                    <button data-slot="button"
     type="button"                         aria-label="Smaller text" x-bind:disabled="disabled || prefs.fontSize === &#039;sm&#039;" x-bind:data-disabled="disabled || prefs.fontSize === &#039;sm&#039; ? &#039;&#039; : undefined" x-on:click="step(-1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
</svg></button>
                    <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022md\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-label="Text size" data-row="fontSize" x-model="fontSizeV" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('sm')"             aria-label="Small" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><span dir="ltr" class="tabular-nums">88%</span></button>
                                                    <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('md')"      data-pressed         aria-label="Medium" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><span dir="ltr" class="tabular-nums">100%</span></button>
                                                    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('lg')"             aria-label="Large" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><span dir="ltr" class="tabular-nums">113%</span></button>
                                                    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('xl')"             aria-label="Extra large" class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs"><span dir="ltr" class="tabular-nums">125%</span></button>
</div>
                    <button data-slot="button"
     type="button"                         aria-label="Larger text" x-bind:disabled="disabled || prefs.fontSize === &#039;xl&#039;" x-bind:data-disabled="disabled || prefs.fontSize === &#039;xl&#039; ? &#039;&#039; : undefined" x-on:click="step(1)" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-border bg-card text-foreground hover:bg-nq-hover size-control-sm p-0">
        <svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M5 12h14"/>
  <path d="M12 5v14"/>
</svg></button>
                </div>
            </div>
        </div>
                <div class="flex min-w-0 flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-4">
            <div class="text-label text-foreground sm:w-32 sm:shrink-0">Line width</div>
            <div class="min-w-0">
                <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022normal\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-label="Line width" data-row="width" x-model="widthV" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('narrow')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Narrow</button>
                                            <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('normal')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Normal</button>
                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('wide')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Wide</button>
                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('full')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Full</button>
</div>
            </div>
        </div>
                <div class="flex min-w-0 flex-col gap-1.5 sm:flex-row sm:items-center sm:gap-4">
            <div class="text-label text-foreground sm:w-32 sm:shrink-0">Line spacing</div>
            <div class="min-w-0">
                <div role="group" data-slot="toggle-group" data-variant="segmented" data-orientation="horizontal"
    x-data="nqToggleGroup(JSON.parse('[\u0022normal\u0022]'), false)" x-modelable="value" x-bind="root"
            aria-label="Line spacing" data-row="spacing" x-model="spacingV" class="flex w-fit max-w-full gap-0.5 rounded-control bg-secondary p-0.5">
    <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('compact')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Compact</button>
                                            <button type="button" data-slot="toggle" aria-pressed="true"
     x-bind="toggle('normal')"      data-pressed         class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Normal</button>
                                            <button type="button" data-slot="toggle" aria-pressed="false"
     x-bind="toggle('relaxed')"             class="inline-flex h-7 shrink-0 items-center justify-center gap-1.5 whitespace-nowrap px-3 text-label text-muted-foreground outline-none transition-colors duration-150 ease-nq hover:text-foreground [&_svg]:size-4 [&_svg]:shrink-0 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-disabled:pointer-events-none data-disabled:opacity-50 rounded-[calc(var(--radius-control)-2px)] data-pressed:bg-card data-pressed:text-foreground data-pressed:shadow-xs">Relaxed</button>
</div>
            </div>
        </div>
        <div class="flex justify-end">
        <button data-slot="button"
     type="button"                  disabled      data-disabled     x-bind:disabled="disabled || isDefault" x-bind:data-disabled="disabled || isDefault ? &#039;&#039; : undefined" x-on:click="reset()" class="inline-flex shrink-0 select-none items-center justify-center gap-2 whitespace-nowrap rounded-control border border-transparent font-sans text-label transition-colors duration-150 ease-nq min-h-[var(--nq-touch-min,0px)] outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 text-foreground hover:bg-nq-hover h-control-sm px-2.5">
        <svg aria-hidden="true" class="rtl:-scale-x-100" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/>
  <path d="M3 3v5h5"/>
</svg>            Reset</button>
    </div>
            <section aria-label="Preview" data-slot="reading-preview" class="min-w-0 overflow-hidden rounded-card border border-border bg-card p-4" style="--reading-scale: 1; --reading-max-width: 68ch; --reading-line-height: 1.6" x-bind:style="styleVars">
            <div class="mx-auto" style="max-width: var(--reading-max-width); font-size: calc(1rem * var(--reading-scale)); line-height: var(--reading-line-height)">
                <h3 class="mb-1 font-semibold text-foreground" style="font-size: 1.25em; line-height: 1.3">A quiet place to read</h3>
                <p class="text-nq-fg-body">Good reading settings disappear. The text is large enough, the lines are short enough, and the space between them lets your eyes rest. Change a setting and this paragraph follows at once.</p>
            </div>
        </section>
    </div>
    <div data-slot="wallpaper-picker" x-data="nqWallpaperPicker('dawn', 20)" x-modelable="selected" class="flex min-w-0 flex-col gap-3">
    <div id="nq-wall-7d6fe681" class="text-label text-foreground">Wallpaper</div>    <div role="radiogroup" x-data="nqRadioGroup('dawn')" x-modelable="value" x-model="selected" x-bind="root"
         aria-labelledby="nq-wall-7d6fe681"                 class="flex flex-col gap-3">
                                <div class="flex flex-col gap-1.5">
                <div class="text-caption text-muted-foreground">Gradients</div>                <div class="grid grid-cols-3 gap-2 sm:grid-cols-4 lg:grid-cols-5">
                                            <button type="button" role="radio" data-slot="wallpaper-none" x-bind="radio('__none__')" aria-label="None" aria-checked="false" tabindex="-1"
                             data-unchecked                                                         class="group relative flex min-w-0 cursor-pointer flex-col gap-2 rounded-card border border-border bg-card p-2 text-start outline-none transition-colors hover:border-nq-line-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-checked:border-primary data-checked:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 p-1">
                            <span aria-hidden="true" class="aspect-[4/3] w-full rounded-control border border-border grid place-items-center bg-nq-surface-soft text-caption text-muted-foreground">None</span>
                            <span data-slot="radio-indicator" x-show="isChecked('__none__')"  style="display: none"  class="absolute end-2 top-2 grid size-4 place-items-center rounded-full bg-primary text-primary-foreground"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                        </button>
                                                                                        <button type="button" role="radio" data-slot="wallpaper-tile" x-bind="radio('dawn')" aria-label="Dawn" title="Dawn" aria-checked="true" tabindex="0"
                             data-checked                                                         class="group relative flex min-w-0 cursor-pointer flex-col gap-2 rounded-card border border-border bg-card p-2 text-start outline-none transition-colors hover:border-nq-line-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-checked:border-primary data-checked:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 p-1">
                            <span aria-hidden="true" class="aspect-[4/3] w-full rounded-control border border-border" style="background: linear-gradient(135deg, #f6d365, #fda085)"></span>
                            <span data-slot="radio-indicator" x-show="isChecked('dawn')"  class="absolute end-2 top-2 grid size-4 place-items-center rounded-full bg-primary text-primary-foreground"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                        </button>
                                                                    <button type="button" role="radio" data-slot="wallpaper-tile" x-bind="radio('sea')" aria-label="Sea" title="Sea" aria-checked="false" tabindex="-1"
                             data-unchecked                                                         class="group relative flex min-w-0 cursor-pointer flex-col gap-2 rounded-card border border-border bg-card p-2 text-start outline-none transition-colors hover:border-nq-line-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-checked:border-primary data-checked:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 p-1">
                            <span aria-hidden="true" class="aspect-[4/3] w-full rounded-control border border-border" style="background: linear-gradient(135deg, #43cea2, #185a9d)"></span>
                            <span data-slot="radio-indicator" x-show="isChecked('sea')"  style="display: none"  class="absolute end-2 top-2 grid size-4 place-items-center rounded-full bg-primary text-primary-foreground"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                        </button>
                                                        </div>
            </div>
                                <div class="flex flex-col gap-1.5">
                <div class="text-caption text-muted-foreground">Photos</div>                <div class="grid grid-cols-3 gap-2 sm:grid-cols-4 lg:grid-cols-5">
                                                                                        <button type="button" role="radio" data-slot="wallpaper-tile" x-bind="radio('city')" aria-label="City" title="City" aria-checked="false" tabindex="-1"
                             data-unchecked                                                         class="group relative flex min-w-0 cursor-pointer flex-col gap-2 rounded-card border border-border bg-card p-2 text-start outline-none transition-colors hover:border-nq-line-strong focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-checked:border-primary data-checked:bg-nq-selected data-disabled:pointer-events-none data-disabled:opacity-50 p-1">
                            <span aria-hidden="true" class="aspect-[4/3] w-full rounded-control border border-border" style="background: center / cover no-repeat url(&quot;/wallpapers/city.jpg&quot;)"></span>
                            <span data-slot="radio-indicator" x-show="isChecked('city')"  style="display: none"  class="absolute end-2 top-2 grid size-4 place-items-center rounded-full bg-primary text-primary-foreground"><svg aria-hidden="true" class="size-3" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M20 6 9 17l-5-5"/>
</svg></span>
                        </button>
                                                                <button type="button"  x-on:click="$refs.file.click()"
                            class="flex aspect-[4/3] flex-col items-center justify-center gap-1 self-start rounded-card border border-border border-dashed p-1 text-caption text-muted-foreground outline-none transition-colors hover:border-nq-line-strong hover:text-foreground focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus disabled:opacity-50">
                            <svg aria-hidden="true" class="size-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
  <path d="M16 5h6"/>
  <path d="M19 2v6"/>
  <path d="M21 11.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7.5"/>
  <path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21"/>
  <circle cx="9" cy="9" r="2"/>
</svg>                            Upload a picture
                        </button>
                                    </div>
            </div>
            </div>
    <input type="file" x-ref="file" accept="image/*" hidden x-on:change="pick($event)">            <div data-slot="wallpaper-dim" x-bind:class="selected === '__none__' ? 'pointer-events-none opacity-50' : ''" >
            <div data-slot="slider" x-data="nqSlider(JSON.parse('{\u0022value\u0022:0.2,\u0022min\u0022:0,\u0022max\u0022:0.6,\u0022step\u0022:0.05,\u0022disabled\u0022:false,\u0022minStepsBetweenThumbs\u0022:0,\u0022format\u0022:{\u0022style\u0022:\u0022percent\u0022}}'))" x-modelable="model"     x-model="dimFraction" class="flex w-full flex-col gap-2 data-disabled:opacity-50">
            <div data-slot="slider-head" class="flex items-baseline justify-between gap-3 text-body-sm">
            <span id="nq-slider-83d38b94-label" class="text-label text-foreground">Dim the picture</span>            <output data-slot="slider-value" class="text-muted-foreground tabular-nums"><bdi x-text="text">20%</bdi></output>        </div>
        <div data-slot="slider-control"  x-on:pointerdown="down($event)" class="flex h-5 w-full touch-none select-none items-center">
        <div data-slot="slider-track" x-ref="track" class="relative h-1.5 w-full rounded-full bg-nq-surface-soft">
            <div data-slot="slider-range" :style="rangeStyle()" style="position: absolute; top: 0; bottom: 0; inset-inline-start: 0px; inset-inline-end: calc(100% - calc(33.3333% + 2.67px))" class="rounded-full bg-primary"></div>
                            <div data-slot="slider-thumb" role="slider" tabindex="0"
                     aria-labelledby="nq-slider-83d38b94-label"                     aria-valuemin="0" aria-valuemax="0.6" aria-orientation="horizontal"
                    :aria-valuenow="values[0]" :aria-valuetext="fmt(values[0])" :data-dragging="dragging && active === 0 ? '' : undefined"
                                        x-on:keydown="key($event, 0)" :style="thumbStyle(0)"
                    style="inset-inline-start: calc(33.3333% + 2.67px); top: 50%; translate: -50% -50%; position: absolute"
                    class="size-4 rounded-full border border-primary bg-card shadow-xs outline-none transition-[box-shadow] duration-150 ease-nq motion-reduce:transition-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-nq-focus data-dragging:shadow-md data-disabled:pointer-events-none"></div>
                                    </div>
    </div>
    </div>
        </div>
    </div></div>
```
