Auxiliares de preferências

Sete pacotes pequenos por framework, cada um dono de uma tarefa do utilizador de ponta a ponta, mais uma barra que reúne cinco deles. Já conheceu o theme-picker; aqui estão locale-picker, text-size-picker e motion-picker (preferências), share-picker e search-picker (ações), date-time-picker (um valor de formulário) e picker-bar.

O que vai construir

Um painel de definições onde os utilizadores escolhem o idioma e o tamanho do texto — escolhas que se aplicam a todo o documento, persistem entre visitas e lhe custam dois componentes e algumas linhas de CSS. Cerca de 15 minutos.

Antes de começar

Conclua primeiro o tutorial do framework da sua stack. Os exemplos abaixo são em Svelte; os auxiliares de React, Vue, Angular, HTML, Nunjucks e Blazor correspondem contrato a contrato.

A forma que partilham

  • Um botão de ícone sem cabeça que abre uma caixa de lista WAI-ARIA APG (ou, no date-time-picker, uma caixa de diálogo de seleção de data) — sem <select>nativo, para que cada framework obtenha o mesmo comportamento de teclado e os mesmos ganchos de estilo.
  • Um gancho de classe por pacote (.locale-picker, .text-size-picker, .share-picker, .date-time-picker), zero CSS distribuído.
  • Seguro para SSR: as escritas no DOM só acontecem no ciclo de vida mount/effect.
  • Limpo para i18n: cada cadeia visível para o utilizador é uma prop.
  • Disponível para Svelte (canónico), React, Vue, Angular, elemento personalizado HTML, Nunjucks e Blazor — o mesmo contrato em cada um.

theme-picker, locale-picker e text-size-picker são donos cada um de uma preferência do utilizador de ponta a ponta (seleção, aplicação ao DOM, localStorage persistência opcional através da prop storageKey ). share-picker e date-time-picker são diferentes na natureza: share-picker é dono de uma ação — não aplica nada ao documento e não persiste nada — e date-time-picker é dono de um valor de formulário, porque uma data num formulário é um dado, não uma preferência.

locale-picker — idioma e direção

Deixa o utilizador escolher uma localização e depois escreve lang e dir na raiz do documento para que a sua biblioteca i18n, o seu CSS e as tecnologias de apoio a sigam. O RTL é detetado automaticamente para árabe, hebraico e outras escritas da direita para a esquerda. Foca-se apenas em sinalizar — a sua biblioteca i18n trata da tradução.

<script>
  import LocalePicker from "@lilydesignsystem/svelte-locale-picker";

  let locale = $state("en-US");
</script>

<LocalePicker
  label="Language"
  locales={["en-US", "fr", "ar", "he", "ja"]}
  bind:value={locale}
  storageKey="my-app-locale"
/>

As opções são rotuladas a partir de uma tabela integrada de nomes de localizações (substituível através de localeLabels), cada opção traz o seu próprio atributo lang para que os leitores de ecrã pronunciem «Français» em francês, e os códigos com sublinhado como en_US são convertidos sem perdas para BCP 47 e de volta en-US. Ligue onChange (ou o valor ligado) ao i18next, ao Paraglide, ao vue-i18n ou ao que usar.

text-size-picker — tamanho controlado pelo leitor

Uma vitória de acessibilidade que demora minutos: o auxiliar define data-text-size="{slug}" na raiz do documento e o seu CSS mapeia cada valor para um tamanho.

<script>
  import TextSizePicker from "@lilydesignsystem/svelte-text-size-picker";
</script>

<TextSizePicker
  label="Text size"
  sizes={["small", "medium", "large", "x-large"]}
  storageKey="my-app-text-size"
/>
:root[data-text-size="small"]   { font-size: 87.5%; }
:root[data-text-size="medium"]  { font-size: 100%; }
:root[data-text-size="large"]   { font-size: 112.5%; }
:root[data-text-size="x-large"] { font-size: 125%; }

Dimensione o seu layout em rem e toda a aplicação escala com a escolha do utilizador. As etiquetas são formadas a partir dos slugs com maiúscula inicial («X Large») ou substituídas com sizeLabels.

Verifique o seu trabalho: escolha «Large» no seletor e o texto da página cresce. Recarregue — continua grande: a escolha ficou guardada em localStorage. Mude o seletor de idioma para árabe e dir="rtl" é definido no documento automaticamente.

share-picker — enviar esta página

Um botão de um só glifo que abre a folha de partilha nativa do dispositivo onde existir e, caso contrário, recorre a uma lista de destinos que fornece, mais uma ação de copiar o URL. Não inclui URLs de redes sociais — quais as redes que pertencem ao seu produto é decisão sua, não do sistema de design.

<script>
  import SharePicker from "@lilydesignsystem/svelte-share-picker";

  const targets = [
    { id: "mastodon", label: "Mastodon",
      href: (url, title) => `https://mastodon.social/share?text=${encodeURIComponent(title)}%20${encodeURIComponent(url)}` },
    { id: "email", label: "Email",
      href: (url, title) => `mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(url)}`, newTab: false },
  ];
</script>

<SharePicker
  label="Share this page"
  title="An article worth reading"
  {targets}
  copyLabel="Copy link"
  copiedLabel="Link copied"
/>

url tem como predefinição location.hrefpelo que o caso comum não precisa de cablagem. Force um caminho com strategy="list" ou strategy="native" quando precisar de comportamento consistente entre plataformas para testes ou capturas de ecrã.

date-time-picker — uma data, uma hora ou ambas

Um campo de texto onde pode escrever, mais um botão de ícone que abre uma caixa de diálogo de seleção de data WAI-ARIA APG. Correto para a localização por construção — os nomes dos meses, os nomes dos dias, o primeiro dia da semana e o relógio de 12 ou 24 horas vêm todos de Intl — e o valor é sempre uma cadeia ISO ordenável e sem fuso horário: "2026-03-15" para mode="date", "09:30" para mode="time", "2026-03-15T09:30" para mode="datetime".

<script>
  import DateTimePicker from "@lilydesignsystem/svelte-date-time-picker";

  let appointment = $state("");
</script>

<DateTimePicker
  inputId="appointment"
  name="appointment"
  label="Choose an appointment date"
  locale="en-GB"
  bind:value={appointment}
  labels={{
    previousYear: "Previous year",
    previousMonth: "Previous month",
    nextMonth: "Next month",
    nextYear: "Next year",
    confirm: "OK",
    cancel: "Cancel",
  }}
/>

O valor ISO é idêntico ao que <input type="date"> envia, pelo que pode trocar o controlo nativo por este ou vice-versa sem tocar no seu backend.

motion-picker — respeite o movimento reduzido

O auxiliar define data-motion="{slug}" na raiz do documento. O seu primeiro valor vem da definição prefers-reduced-motion do sistema operativo do visitante — sempre, sem uma bandeira de adesão, porque o movimento é uma necessidade real de acessibilidade (WCAG 2.3.3). O seu CSS e os seus scripts decidem o que é suprimido.

<script>
  import MotionPicker from "@lilydesignsystem/svelte-motion-picker";
</script>

<MotionPicker
  label="Motion"
  motions={["no-preference", "reduce"]}
  storageKey="my-app-motion"
/>
:root[data-motion="reduce"] *,
:root[data-motion="reduce"] *::before,
:root[data-motion="reduce"] *::after {
  animation: none !important;
  transition: none !important;
  scroll-behavior: auto !important;
}

search-picker — uma caixa de pesquisa que não atrapalha

Um botão de ícone que abre um campo de pesquisa. Premir Enter, ou o botão ⏎ junto ao campo, navega com um simples pedido GET para /?<text> (o texto é aparado e codificado como URI; uma pesquisa vazia não leva a lado nenhum). Os seus três nomes acessíveis são obrigatórios, porque o auxiliar não inclui inglês.

<script>
  import SearchPicker from "@lilydesignsystem/svelte-search-picker";
  import { goto } from "$app/navigation";
</script>

<SearchPicker
  label="Search this site"
  inputLabel="Search terms"
  submitLabel="Search"
  placeholder="Search…"
  action="/search"
  navigate={(href) => goto(href)}
/>

action altera o caminho (/search?foo), e navigate mantém a navegação dentro da aplicação; sem ele, o auxiliar chama location.assign.

picker-bar — toda a linha do cabeçalho de uma vez

picker-bar renderiza pesquisa, tema, localização, tamanho do texto e partilha numa só linha, com o conjunto completo de temas de referência e a escala de texto de sete passos já ligados. Cada nome acessível chega num único objeto labels obrigatório.

<script>
  import PickerBar from "@lilydesignsystem/svelte-picker-bar";
</script>

<PickerBar
  labels={{
    search: "Search", searchInput: "Search terms", searchSubmit: "Search",
    theme: "Theme", locale: "Language", textSize: "Text size", share: "Share",
  }}
  themesUrl="/themes/"
  locales={["en", "fr", "es"]}
  shareTargets={targets}
/>

Alcance qualquer seletor embrulhado com searchProps, themeProps, localeProps, textSizeProps e shareProps.

As dicas já vêm integradas

Cada seletor mostra a sua etiqueta numa dica quando o ponteiro repousa no botão ou o botão tem foco de teclado. A dica permanece enquanto o ponteiro se move para ela, Escape fecha-a onde quer que esteja o foco, e nunca aparece enquanto a janela do seletor está aberta. Leva o mesmo texto que o nome acessível do botão, pelo que não há nada a configurar nem a traduzir duas vezes; os temas de referência estilizam-na.

Um painel de definições num só lugar

Os quatro auxiliares de preferências compõem-se naturalmente:

<fieldset class="fieldset">
  <legend>Preferences</legend>
  <ThemePicker    label="Theme"     themesUrl="/themes/" themes={themes} storageKey="pref-theme" />
  <LocalePicker   label="Language"  locales={locales}    storageKey="pref-locale" />
  <TextSizePicker label="Text size" sizes={sizes}        storageKey="pref-text-size" />
  <MotionPicker   label="Motion"    motions={motions}    storageKey="pref-motion" />
</fieldset>

Verificado: os excertos de locale-picker, text-size-picker, share-picker, date-time-picker e do painel combinado foram compilados individualmente contra os pacotes 0.1.1 publicados, através de svelte/server (2026-08-29); os excertos de motion-picker, search-picker e picker-bar foram renderizados contra os componentes dos próprios pacotes em 2026-10-06.

Onde obtê-los

# one directory per package in the monorepo, e.g.
git clone https://github.com/LilyDesignSystem/lily-design-system
cd lily-design-system/lily-design-system-svelte-theme-picker
# or lily-design-system-react-theme-picker, -vue- / -angular- / -html- / -web-components- / -nunjucks- / -blazor-

Cada pacote tem o seu próprio spec, testes mapeados para as cláusulas do spec, exemplos executáveis (incluindo padrões de cookies para SSR) e um pipeline de compilação e publicação. Os pacotes Svelte são os canónicos; em caso de dúvida, os seus contratos prevalecem.

Passos seguintes

Dúvidas pelo caminho? A página de ajuda está cheia de respostas — e se algum passo puder ser mais claro, diga-nos e melhoraremos o tutorial com todo o gosto.