Apariencia

La sección Apariencia de los ajustes de un producto de IA: tema, acento, tinte de fondo, densidad, fuente del chat, tamaños de texto y opciones de accesibilidad, todo guardado como borrador hasta que se guarda.

Appearance agrupa todo lo relativo al aspecto de la app en una sola sección. Las tarjetas de tema muestran una pequeña ventana en cada tema, dibujada con el acento que hayas elegido, para que la elección sea clara antes de guardar.

System sigue al dispositivo e indica cuál es en este momento. High contrast refuerza bordes y texto y mantiene los fondos neutros, así que el control deslizante de tinte se desactiva mientras está elegido.

El tono del acento es una pista de color real con ocho selecciones rápidas debajo, y la lectura nombra el color, como Blue 250°. Background tint mezcla ese acento en las superficies. Las tarjetas de densidad y fuente del chat muestran una muestra de cada opción, y los dos tamaños de texto son steppers con límites.

Reduce motion y Reduce transparency indican cuándo el dispositivo ya pide alguna de las dos.

Todo es un borrador hasta que se guarda. La barra de guardado de Settings aparece en cuanto algo difiere, Discard recupera el aspecto guardado y Reset devuelve cada opción a su valor predeterminado como un borrador que aún puedes descartar.

  1. Añade el registro Pro a components.json

    components.json
    {
      "registries": {
        "@hextaui-pro": {
          "url": "https://hextaui.com/r/pro/{name}.json",
          "headers": {
            "Authorization": "Bearer ${HEXTAUI_PRO_TOKEN}"
          }
        }
      }
    }
  2. Añade tu token

    Crea un token en tu página de cuenta y colócalo en .env.local como HEXTAUI_PRO_TOKEN.

  3. Añade el bloque

    pnpm dlx shadcn@latest add @hextaui-pro/appearance

Guarda en tu API

Pasa los valores guardados y un onSave asíncrono. No se envía nada hasta que alguien pulsa Save en la barra. Lanza un error para mostrar tu mensaje en la barra y conservar sus elecciones.

"use client"

import { IconPalette } from "@tabler/icons-react"

import { SettingsSection, SettingsShell } from "../settings/settings"
import { AppearanceSettings, type AppearanceValues } from "@/components/blocks/appearance/appearance"

export function AppearancePage({ appearance }: { appearance: AppearanceValues }) {
  return (
    <SettingsShell
      sections={[{ id: "appearance", label: "Appearance", icon: <IconPalette /> }]}
      className="h-svh"
    >
      <SettingsSection id="appearance">
        <AppearanceSettings
          values={appearance}
          onSave={async (values) => {
            const response = await fetch("/api/appearance", {
              method: "PUT",
              body: JSON.stringify(values),
            })
            if (!response.ok) throw new Error("Check your connection and try again.")
          }}
        />
      </SettingsSection>
    </SettingsShell>
  )
}

Aplica el aspecto guardado

Una vez guardados, aplica los valores a tu app según cómo gestione sus temas. normalizeAppearance repara todo lo almacenado por una versión anterior, así que un valor incorrecto nunca rompe la página.

"use client"

import * as React from "react"

import { AppearanceSettings, normalizeAppearance, type AppearanceValues } from "@/components/blocks/appearance/appearance"

function applyAppearance(values: AppearanceValues) {
  const root = document.documentElement
  const systemDark = window.matchMedia("(prefers-color-scheme: dark)").matches
  const dark = values.theme === "dark" || values.theme === "contrast" || (values.theme === "system" && systemDark)
  root.classList.toggle("dark", dark)
  root.dataset.contrast = values.theme === "contrast" ? "more" : "normal"
  root.dataset.density = values.density
  root.dataset.chatFont = values.chatFont
  root.dataset.motion = values.reduceMotion ? "reduce" : "full"
  root.dataset.transparency = values.reduceTransparency ? "reduce" : "full"
  root.style.setProperty("--accent-hue", String(values.hue))
  root.style.setProperty("--accent-tint", `${values.intensity}%`)
  root.style.setProperty("--text-interface", `${values.interfaceSize}px`)
  root.style.setProperty("--text-code", `${values.codeSize}px`)
}

export function AppearanceForm({ stored }: { stored: Partial<AppearanceValues> | null }) {
  const [saved, setSaved] = React.useState(() => normalizeAppearance(stored))

  React.useEffect(() => applyAppearance(saved), [saved])

  return (
    <AppearanceSettings
      values={saved}
      onSave={async (values) => {
        const response = await fetch("/api/appearance", {
          method: "PUT",
          body: JSON.stringify(values),
        })
        if (!response.ok) throw new Error("Check your connection and try again.")
        setSaved(values)
      }}
    />
  )
}

Anatomía

Las partes que compones, de fuera hacia dentro.

ParteDescripción
AppearanceSettingsEl contenido de la sección: los grupos de opciones, conectados a la barra de guardado del shell.
normalizeAppearanceConvierte valores almacenados o parciales en valores válidos: limita los tamaños, da la vuelta al tono y recurre a un valor por defecto para nombres desconocidos.
appearanceDefaultsLos valores a los que vuelve Reset cuando no pasas los tuyos.

AppearanceSettings

Úsalo dentro de un SettingsSection. También acepta todas las props de div.

PropTipoPredeterminado
valuesLo que está guardado ahora: { theme, hue, intensity, density, chatFont, interfaceSize, codeSize, reduceMotion, reduceTransparency }. Los valores inválidos se reparan, y la reparación no cuenta como un cambio sin guardar.
AppearanceValues–
onSaveGuarda el borrador. Lanza un error para mostrar el mensaje en la barra de guardado y conservar el borrador.
(values) => void | errors | Promise<void | errors>–
defaultsA qué vuelve Reset.
AppearanceValuesappearanceDefaults
PropTipoPredeterminado
themeSystem sigue prefers-color-scheme.
"system" | "light" | "dark" | "contrast""system"
hueEl tono OKLCH del acento, de 0 a 359.
number250
intensityCuánto tiñe el acento los fondos, de 0 a 100.
number30
densityEspacio entre los mensajes y dentro de ellos.
"compact" | "comfortable""comfortable"
chatFontLa fuente de los mensajes.
"sans" | "serif" | "mono""sans"
interfaceSizeTamaño del texto en píxeles, de 12 a 18.
number14
codeSizeTamaño del código en píxeles, de 11 a 20.
number13
reduceMotionDetiene los spinners y los paneles deslizantes.
booleanfalse
reduceTransparencyFondos sólidos en lugar de difuminados.
booleanfalse
KeyAcción
←→En las tarjetas de tema, densidad o fuente, elige la tarjeta anterior o siguiente. En la pista de tono, mueve un grado.
Shift→En la pista de tono, mueve 15 grados. Page Up y Page Down hacen lo mismo.
HomeEndEn la pista de tono, salta a cualquiera de los extremos.
↑↓En un campo de tamaño de texto, cambia el tamaño en un píxel.
⌘SGuarda mientras haya cambios sin guardar. Ctrl+S en Windows y Linux.
  • Cada grupo de tarjetas es un radio group con el nombre de su fila, y cada tarjeta se nombra con su propia etiqueta, de modo que los lectores de pantalla oyen «Dark, radio, 3 of 4».
  • La pista de tono lee su color por nombre, como «Blue, 250 degrees», y las selecciones rápidas son botones toggle nombrados por color.
  • Reset mantiene el foco después de ejecutarse y anuncia que los valores predeterminados han vuelto y hay que guardarlos.

Construido con

Los componentes gratuitos de HextaUI con los que está hecho Appearance. Cada uno se instala por separado.

Código

2 archivos, añadidos a components/blocks/appearance.