환경설정 헬퍼

프레임워크마다 작은 패키지 7개가 있고, 각각 사용자의 한 가지 작업을 처음부터 끝까지 맡습니다. 그중 5개를 묶어 주는 바도 있습니다. 이미 theme-picker는 소개했습니다. 여기서는 locale-picker, text-size-picker, motion-picker(환경설정), share-picker, search-picker(동작), date-time-picker(폼 값), 그리고 picker-bar를 다룹니다.

만들 것

사용자가 언어와 텍스트 크기를 고르는 설정 패널 — 선택은 문서 전체에 적용되고, 방문해도 유지되며, 컴포넌트 두 개와 몇 줄의 CSS면 됩니다. 약 15분 걸립니다.

시작하기 전에

먼저 사용하는 스택의 프레임워크 튜토리얼 을 마치세요. 아래 예제는 Svelte이지만, React, Vue, Angular, HTML, Nunjucks, Blazor 헬퍼도 계약 단위로 똑같이 맞춰져 있습니다.

공통된 형태

  • WAI-ARIA APG 리스트박스(date-time-picker는 날짜 선택 대화상자)를 여는 헤드리스 아이콘 버튼입니다 — 네이티브 <select>를 쓰지 않으므로 모든 프레임워크에서 키보드 동작과 스타일 훅이 똑같습니다.
  • 패키지마다 클래스 훅 하나(.locale-picker, .text-size-picker, .share-picker, .date-time-picker)이며, CSS는 전혀 포함하지 않습니다.
  • SSR 안전: DOM 쓰기는 마운트/이펙트 수명 주기에서만 일어납니다.
  • i18n 친화적: 사용자에게 보이는 모든 문자열이 prop입니다.
  • Svelte(기준), React, Vue, Angular, HTML 커스텀 요소, Nunjucks, Blazor에서 쓸 수 있고 — 모두 같은 계약입니다.

theme-picker, locale-picker, text-size-picker는 각각 사용자 환경설정 하나를 처음부터 끝까지(선택, DOM 적용, 선택 사항인 localStorage 지속성을 storageKey prop으로) 맡습니다. share-picker와 date-time-picker는 성격이 다릅니다. share-picker는 동작 을 맡아서 — 문서에 아무것도 적용하지 않고 아무것도 저장하지 않으며 — date-time-picker는 폼 값을 맡습니다. 폼 안의 날짜는 환경설정이 아니라 데이터이기 때문입니다.

locale-picker — 언어와 방향

사용자가 로캘을 고르게 하고, lang 와 dir 를 문서 루트에 기록해 i18n 라이브러리, CSS, 보조 기술이 모두 따라오게 합니다. 아랍어, 히브리어 등 오른쪽에서 왼쪽으로 쓰는 문자는 자동으로 감지됩니다. 역할은 신호 전달뿐이며 번역은 i18n 라이브러리가 맡습니다.

<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"
/>

옵션 레이블은 내장된 로캘 이름 표에서 가져오며( localeLabels로 재정의할 수 있음), 각 옵션은 자체 lang 특성을 가지므로 스크린 리더가 "Français"를 프랑스어로 발음합니다. 또한 다음과 같은 밑줄 코드는 en_US BCP 47로 손실 없이 왕복 변환됩니다 en-US. onChange (또는 바인딩된 값)를 i18next, Paraglide, vue-i18n 등 쓰는 것에 연결하세요.

text-size-picker — 읽는 사람이 조절하는 크기

몇 분이면 되는 접근성 개선입니다. 이 헬퍼는 문서 루트에 data-text-size="{slug}" 를 설정하고, CSS가 각 값을 크기로 연결합니다.

<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%; }

레이아웃을 rem 로 잡으면 앱 전체가 사용자의 선택에 따라 커지고 작아집니다. 레이블은 슬러그에서 타이틀 케이스로 만들어지거나("X Large") sizeLabels.

작동 확인: 로 재정의할 수 있습니다. 피커에서 "Large"를 고르면 페이지 텍스트가 커집니다. 새로 고쳐도 여전히 큽니다 — 선택이 다음에 저장되었기 때문입니다: localStorage. 언어 피커를 아랍어로 바꾸면 dir="rtl" 가 문서에 자동으로 설정됩니다.

share-picker — 이 페이지 보내기

글리프 하나짜리 버튼으로, 가능한 환경에서는 기기의 네이티브 공유 시트를 열고, 그렇지 않으면 직접 제공한 대상 목록과 URL 복사 동작으로 대체됩니다. 소셜 네트워크 URL은 포함되어 있지 않습니다 — 어떤 네트워크가 제품에 맞는지는 디자인 시스템이 아니라 당신이 정할 일입니다.

<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 의 기본값은 location.href이므로 일반적인 경우에는 연결 작업이 필요 없습니다. 테스트나 스크린샷에서 플랫폼 간에 일관된 동작이 필요할 때는 strategy="list" 또는 strategy="native" 로 경로를 강제할 수 있습니다.

date-time-picker — 날짜, 시간, 또는 둘 다

직접 입력할 수 있는 텍스트 필드와, WAI-ARIA APG 날짜 선택 대화상자를 여는 아이콘 버튼입니다. 구조적으로 로캘에 맞습니다 — 월 이름, 요일 이름, 한 주의 시작 요일, 12시간제/24시간제는 모두 Intl 에서 가져오고 — 값은 언제나 정렬 가능하고 시간대가 없는 ISO 문자열입니다: "2026-03-15" 는 mode="date", "09:30" 는 mode="time", "2026-03-15T09:30" 는 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",
  }}
/>

ISO 값은 다음이 전송하는 값과 같습니다: <input type="date"> 따라서 백엔드를 건드리지 않고 네이티브 컨트롤과 서로 바꿔 쓸 수 있습니다.

motion-picker — 동작 줄이기 설정 존중하기

이 헬퍼는 문서 루트에 data-motion="{slug}" 를 설정합니다. 첫 값은 방문자 운영 체제의 prefers-reduced-motion 설정에서 가져오며 — 항상, 옵트인 플래그 없이 — 움직임은 실제 접근성 요구이기 때문입니다(WCAG 2.3.3). 무엇을 억제할지는 CSS와 스크립트가 정합니다.

<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 — 방해되지 않는 검색창

검색 필드를 여는 아이콘 버튼입니다. Return 키나 필드 옆의 ⏎ 버튼을 누르면 일반 GET 요청으로 다음 주소로 이동합니다: /?<text> (텍스트는 앞뒤 공백이 제거되고 URI 인코딩되며, 빈 쿼리는 아무 곳으로도 이동하지 않습니다). 헬퍼에 영어가 포함되어 있지 않으므로 접근 가능한 이름 3개는 필수입니다.

<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 는 경로를 바꾸고(/search?foo), navigate 는 이동을 앱 안에 머물게 합니다. 이것이 없으면 헬퍼는 다음을 호출합니다: location.assign.

picker-bar — 헤더 행 전체를 한 번에

picker-bar 검색, 테마, 로캘, 텍스트 크기, 공유를 한 행에 렌더링하며, 참조 테마 전체와 7단계 텍스트 스케일이 미리 연결되어 있습니다. 접근 가능한 이름은 모두 필수 labels 객체 하나로 전달합니다.

<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}
/>

감싸진 피커에는 다음으로 접근합니다: searchProps, themeProps, localeProps, textSizeProps 와 shareProps.

툴팁이 기본으로 들어 있습니다

각 피커는 포인터가 버튼 위에 있거나 버튼에 키보드 포커스가 있을 때 레이블을 툴팁으로 보여 줍니다. 포인터가 툴팁 위로 옮겨 가도 유지되고, 포커스가 어디에 있든 Escape로 닫을 수 있으며, 피커의 팝업이 열려 있는 동안에는 표시되지 않습니다. 버튼의 접근 가능한 이름과 같은 텍스트를 쓰므로 설정할 것도, 두 번 번역할 것도 없습니다. 스타일은 참조 테마가 입힙니다.

설정 패널을 한 곳에

환경설정 헬퍼 4개는 자연스럽게 어울립니다:

<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>

확인 완료: locale-picker, text-size-picker, share-picker, date-time-picker 및 결합 패널 스니펫은 공개된 0.1.1 패키지를 대상으로 개별 컴파일하여 확인했습니다. 사용한 명령: svelte/server (2026-08-29). motion-picker, search-picker, picker-bar 스니펫은 2026-10-06에 패키지 자체 컴포넌트로 렌더링하여 확인했습니다.

받을 수 있는 곳

# 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-

각 패키지에는 전용 사양, 사양 조항에 대응하는 테스트, 실행 가능한 예제(SSR 쿠키 패턴 포함), 빌드 및 배포 파이프라인이 있습니다. Svelte 패키지가 기준이며, 헷갈리면 그 계약이 우선합니다.

다음 단계

진행하다가 궁금한 점이 있다면 도움말 페이지 에 답이 많이 있습니다. 어떤 단계가 더 명확해질 수 있다면 알려 주세요 . 기꺼이 튜토리얼을 개선하겠습니다.