Langsung ke isi
Tema

Bab 07 · Komponen

Komponen, A sampai Z.

Acuan untuk setiap halaman — situs, game, dan Studio. Satu bagian per komponen lib/ui, urut abjad, dengan demo komponen produksi, cuplikan pemakaian, dan catatan keyboard. Bentuknya terinspirasi shadcn/ui; API-nya mengikuti Svelte 5 dan kebutuhan situs ini.

Komponen

60

Mandiri

55

Komposisi

5

Helper

4

07.1 — Cara memakai

Impor, rangkai, selesai.

Satu berkas = satu komponen. Fungsi dan tipe tinggal di berkas .ts di sebelahnya (toast, MenuEntry, SelectOption). Pakai yang ada dulu; bila sebuah pola muncul tiga kali, jadikan komponen di lib/ui dan tambahkan bagiannya di sini.

ts
import Button from '#lib/ui/button.svelte';
import Field from '#lib/ui/field.svelte';
import { toast } from '#lib/ui/toast.svelte.ts';
  • Bits UI di bawahnya

    Fokus terkunci, Esc menutup, panah berpindah — perilaku ARIA tidak ditulis ulang.

  • Token, bukan warna mentah

    bg, fg, surface, border-strong, accent dari Tailwind v4 — tema terang/gelap ikut sendiri.

  • Aksesibel sejak awal

    Label wajib, cincin fokus yang sama, area sentuh ≥ 24 px, hormat reduced motion.

  • Jalan tanpa JavaScript

    Form POST, details, dan select asli dulu; JavaScript menambah, bukan menggantikan.

Resep komposisi

Data table

Table + SortableTh + Pagination. Pemanggil menyimpan urutan dan halaman; tabel tetap <table> asli.

svelte
<Table caption="Invoice terakhir.">
	<thead><tr>
		<th scope="col">Nomor</th>
		<SortableTh label="Total" align="right" dir={arah} onSort={urutkan} />
	</tr></thead>
	<tbody>{#each halamanIni as r (r.no)}…{/each}</tbody>
</Table>
<Pagination bind:page count={semua.length} perPage={20} />

Form

Field + Input + Button type="submit". Label, petunjuk, dan galat server tersambung otomatis.

svelte
<form method="POST" use:enhance>
	<Field label="Email" error={form?.galat?.email} required>
		<Input type="email" name="email" />
	</Field>
	<Button type="submit" loading={mengirim}>Kirim</Button>
</form>

Saringan Studio

ButtonGroup menempel + Button href. Keadaan tinggal di URL, jadi tetap tautan dengan aria-current.

svelte
<ButtonGroup label="Saringan" menempel>
	{#each SARINGAN as s (s.nilai)}
		<Button href="?status={s.nilai}" size="sm"
			variant={aktif === s.nilai ? 'secondary' : 'outline'}
			aria-current={aktif === s.nilai ? 'page' : undefined}>{s.nama}</Button>
	{/each}
</ButtonGroup>

07.2 — Komponen A–Z

60 dari 60 komponen

accordion

mandiri Tata letak

Daftar tanya-jawab yang membuka satu bagian sekaligus, untuk FAQ.

ts
import Accordion from '#lib/ui/accordion.svelte';
FAQ · satu terbuka
Company profile biasanya 2–4 minggu sejak materi lengkap.

terbuka: lama

Pakai

svelte
<Accordion bind:value items={[{ value: 'a', title: 'Pertanyaan', content: 'Jawaban.' }]} />

Aksesibilitas & keyboard

  • Tiap judul adalah tombol di dalam heading; Enter/Spasi membuka dan menutup.
  • Satu bagian terbuka sekaligus; membuka bagian lain menutup yang sebelumnya.
  • Isi berupa teks biasa — untuk isi kaya, pakai Collapsible.

alert

mandiri Umpan balik

Pesan yang menetap di halaman: info, berhasil, peringatan, atau galat.

ts
import Alert from '#lib/ui/alert.svelte';
Empat varian

Antrean penuh bulan ini

Slot berikutnya November 2026.

Invoice lunas

Pembayaran Rp 6.500.000 diterima 17 Okt.

Jatuh tempo 3 hari lagi

INV/2026/014 belum dibayar.
Judul saja

Perubahan tersimpan.

Pakai

svelte
<Alert variant="warning" title="Jatuh tempo 3 hari lagi">INV/2026/014 belum dibayar.</Alert>

Aksesibilitas & keyboard

  • Hanya varian danger yang memakai role="alert" (diumumkan seketika); varian lain role="status".
  • Warna selalu ditemani ikon dan judul — maknanya tidak bergantung pada warna saja.

alert-dialog

mandiri Overlay

Konfirmasi tindakan yang tidak bisa dibatalkan, dengan fokus awal di "Batal".

ts
import AlertDialog from '#lib/ui/alert-dialog.svelte';
Konfirmasi hapus
Label sendiri

Pakai

svelte
<AlertDialog
	title="Hapus INV/2026/014?"
	description="Tindakan ini tidak bisa dibatalkan."
	confirmLabel="Ya, hapus"
	onConfirm={hapus}
>
	{#snippet trigger(props)}<Button {...props} variant="danger">Hapus</Button>{/snippet}
</AlertDialog>

Aksesibilitas & keyboard

  • Fokus awal jatuh ke "Batal", bukan ke tombol bahaya; Tab berputar di dalam dialog.
  • Esc membatalkan; klik di luar TIDAK menutup — keputusan harus disengaja.
  • Saat tertutup, fokus kembali ke pemicunya.

app-shell

komposisi Tata letak

Kerangka Studio: kolom samping tetap di layar lebar, panel geser di ponsel.

ts
import AppShell from '#lib/ui/app-shell.svelte';
Kerangka Studio · perkecil jendela untuk panel geser

Hari ini

Prospek baru

4

Belum dibayar

9,8 jt

Pakai

svelte
<AppShell>
	{#snippet brand()}<p class="display text-2xl">Studio</p>{/snippet}
	{#snippet sidebar()}<NavList label="Kerja" items={menu} active="today" />{/snippet}
	{#snippet header()}<p class="display text-2xl">Hari ini</p>{/snippet}
	Isi halaman…
</AppShell>

Aksesibilitas & keyboard

  • Satu snippet sidebar dipakai untuk kolom samping dan panel ponsel — tidak ada dua menu yang bisa berbeda.
  • Tombol menu ponsel berlabel "Buka menu"; panel menutup saat tautan di dalamnya diklik.
  • Isi utama berada di <main>; kolom samping menempel setinggi layar. Di dalam <main> lain (seperti demo ini) pakai elemenIsi="div".

aspect-ratio

mandiri Tata letak

Bingkai yang menjaga perbandingan lebar dan tinggi isinya.

ts
import AspectRatio from '#lib/ui/aspect-ratio.svelte';
Tiga rasio
16 : 9
4 : 3
1 : 1

Pakai

svelte
<AspectRatio ratio={16 / 9} class="border-2 border-border">
	<img src={gambar} alt="Tangkapan layar beranda" class="size-full object-cover" />
</AspectRatio>

Aksesibilitas & keyboard

  • Hanya bingkai; teks alternatif tetap tanggung jawab isi (img alt).
  • Rasio ≤ 0 jatuh ke 1 : 1 supaya bingkai tidak runtuh.

avatar

mandiri Data

Foto orang atau inisialnya bila gambar tidak ada atau gagal dimuat.

ts
import Avatar from '#lib/ui/avatar.svelte';
Tiga ukuran · inisial
Sari Wulandari
Sari Wulandari
Sari Wulandari
Dengan nama
Budi Santoso

Budi Santoso

CV Bangun Jaya

Pakai

svelte
<Avatar src={foto} alt="Sari Wulandari" fallback="SW" size="lg" />

Aksesibilitas & keyboard

  • alt wajib; saat gambar gagal, inisial disembunyikan dari pembaca layar dan alt dibacakan.
  • Gambar ditampilkan hitam-putih supaya selaras dengan palet editorial.

badge

mandiri Data

Label status pendek: lunas, terlambat, baru.

ts
import Badge from '#lib/ui/badge.svelte';
Enam varian
Netral Solid Aksen Lunas Segera Terlambat
Dengan ikon
Beres 2 hari lagi

Pakai

svelte
<Badge variant="success">Lunas</Badge>

Aksesibilitas & keyboard

  • Badge adalah teks, bukan kontrol — jangan diberi onclick; pakai Button bila bisa diklik.
  • Status jangan hanya warna: teksnya sendiri harus menjelaskan.

breadcrumb

mandiri Navigasi

Jejak lokasi halaman; item terakhir adalah halaman saat ini.

ts
import Breadcrumb from '#lib/ui/breadcrumb.svelte';
Tiga tingkat
Label sendiri

Pakai

svelte
<Breadcrumb
	label={m.remah_roti()}
	items={[{ label: 'Studio', href: '/studio' }, { label: 'Invoice', href: '/studio/invoices' }, { label: 'INV/2026/014' }]}
/>

Aksesibilitas & keyboard

  • Dibungkus <nav> berlabel (bawaan "Remah roti"; situs publik mengoper versi bahasanya).
  • Item terakhir bertanda aria-current="page" dan tidak ditautkan.
  • Pemisah "/" disembunyikan dari pembaca layar.

button

mandiri Aksi

Tombol atau tautan bergaya tombol, dalam delapan varian dan lima ukuran.

ts
import Button from '#lib/ui/button.svelte';
Varian
  • primary — satu per tampilan: tindakan utama (kobalt).
  • accent — sorotan khusus (kuning arsip), bukan pengganti primary.
  • secondary / outline / ghost — pendamping; makin ringan berurutan.
  • link — tindakan di dalam kalimat; tautan — pola "LIHAT SEMUA →" di bawah bagian.
  • danger — hanya tindakan merusak, dengan konfirmasi.
Matriks keadaan · normal, nonaktif, memuat
Setiap varian tombol dalam keadaan normal, nonaktif, dan memuat.
VarianNormalNonaktifMemuat
primary
accent
secondary
outline
ghost
link
tautan
danger
Ukuran & ikon
Keadaan & tautan

Pakai

svelte
<Button onclick={simpan} loading={menyimpan}>Simpan</Button>
<Button variant="outline" href="/studio/invoices">Semua invoice</Button>
<Button variant="tautan" size="sm" href="/process">Lihat prosesnya <ArrowRight /></Button>
<Button variant="ghost" size="icon-sm" aria-label="Tutup"><X /></Button>
<Button type="submit" name="tema" value="dark">Gelap</Button>

Aksesibilitas & keyboard

  • type bawaan "button" — tidak diam-diam mengirim form; tulis type="submit" bila memang mengirim.
  • Dengan href menjadi <a>: Enter mengikuti tautan, tanpa atribut disabled.
  • loading memasang aria-busy dan menonaktifkan klik, mencegah kirim ganda.
  • Tombol ikon saja WAJIB aria-label; icon-sm (32 px) tetap di atas area sentuh 24 px.

button-group

mandiri Aksi

Kelompok tombol berlabel; bisa menempel menjadi satu segmen.

ts
import ButtonGroup from '#lib/ui/button-group.svelte';
Menempel · saringan (tautan dengan aria-current)
Menempel · segmen tema (aria-pressed)

Di situs, segmen ini berupa form POST ke /tema dan bekerja tanpa JavaScript.

Renggang · bilah alat ikon

Pakai

svelte
<!-- Saringan Studio: keadaan di URL, jadi tetap tautan -->
<ButtonGroup label="Saringan" menempel>
	<Button href="?status=baru" size="sm" variant={aktif ? 'secondary' : 'outline'} aria-current={aktif ? 'page' : undefined}>Baru</Button>
	…
</ButtonGroup>

<!-- Tema tanpa JavaScript: form POST biasa -->
<form method="POST" action="/tema">
	<ButtonGroup label="Tema" menempel>
		<Button type="submit" name="tema" value="dark" size="sm" variant={aktif ? 'primary' : 'outline'} aria-pressed={aktif}>Gelap</Button>
	</ButtonGroup>
</form>

Aksesibilitas & keyboard

  • role="group" dengan label wajib — pembaca layar menyebut nama kelompoknya.
  • Pilihan yang aktif ditandai aria-pressed (tombol) atau aria-current (tautan), bukan warna saja.
  • Saat menempel, tombol yang difokus/disorot naik ke atas supaya cincin fokusnya utuh.
  • Butuh satu pilihan dengan panah kiri/kanan? Pakai ToggleGroup.

calendar

mandiri Formulir

Kalender bulan untuk memilih satu tanggal, bernilai string ISO.

ts
import Calendar from '#lib/ui/calendar.svelte';
Satu tanggal
MiSeSeRaKaJuSa
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

nilai: 2026-10-19

Pakai

svelte
<Calendar bind:value={tanggal} label="Tanggal rapat" />

Aksesibilitas & keyboard

  • Panah berpindah hari, PageUp/PageDown berpindah bulan, Home/End ke awal/akhir minggu.
  • Hari ini ditandai tebal beraksen; hari terpilih berlatar penuh.
  • Butuh isian yang bisa diketik? Pakai DatePicker.

card

mandiri Tata letak

Panel berbingkai dengan eyebrow, judul, isi, dan kaki opsional.

ts
import Card from '#lib/ui/card.svelte';
Lengkap · eyebrow, judul, isi, kaki

Company profile · mulai 2 Okt

Toko Kue Bu Sari

Sari Wulandari

Sari Wulandari

Pemilik

Tahap 3 / 7
Padat · dengan deskripsi

Catatan

Materi belum lengkap

Foto produk dan teks tentang usaha masih ditunggu dari klien.

Kartu tanpa kepala, hanya isi — dengan bayangan keras.

Pakai

svelte
<Card eyebrow="Company profile · mulai 2 Okt" title="Toko Kue Bu Sari" class="shadow-pop">
	Isi kartu…
	{#snippet footer()}<Button size="sm" variant="ghost" class="ml-auto">Buka</Button>{/snippet}
</Card>

Aksesibilitas & keyboard

  • Berupa <article>; judulnya <h3> — pastikan urutan heading halaman tetap masuk akal.
  • Kelas tambahan digabung lewat cn(): class="border-border" menimpa garis bawaan, tidak bertabrakan.

checkbox

mandiri Formulir

Centang ya/tidak, termasuk keadaan sebagian (indeterminate).

ts
import Checkbox from '#lib/ui/checkbox.svelte';
Keadaan

Tiga dari lima sudah dipilih.

Galat

Centang untuk melanjutkan.

Pakai

svelte
<Field label="Saya setuju dengan ketentuan" inline>
	<Checkbox name="setuju" bind:checked={setuju} />
</Field>

Aksesibilitas & keyboard

  • Spasi mencentang; di dalam Field inline, label di kanan ikut bisa diklik.
  • indeterminate dibacakan "sebagian tercentang" — untuk "pilih semua".
  • Area sentuh diperluas 3 px di setiap sisi walau kotaknya 18 px.

code-block

mandiri Data

Cuplikan kode yang bisa digulir dengan keyboard dan disalin.

ts
import CodeBlock from '#lib/ui/code-block.svelte';
Pendek
bash
bunx vitest run
Panjang · gulir mendatar
ts
const rupiah = (n: number) => `Rp ${n.toLocaleString('id-ID')}`;
const total = baris.reduce((jumlah, b) => jumlah + b.harga * b.jumlah, 0); // baris panjang yang perlu digulir mendatar di layar sempit

Pakai

svelte
<CodeBlock bahasa="ts" kode={cuplikan} />

Aksesibilitas & keyboard

  • <pre> bisa difokus (Tab) lalu digulir dengan panah — kode panjang tidak terkunci.
  • Hasil salin diumumkan lewat role="status"; bila papan klip ditolak, ada petunjuk memilih manual.
  • Tanpa pewarnaan sintaks: kontras teks tetap penuh di kedua tema.

collapsible

mandiri Tata letak

Bagian buka-tutup di atas <details> asli, bekerja tanpa JavaScript.

ts
import Collapsible from '#lib/ui/collapsible.svelte';
Bawaan · dengan ikon
Karya baru

open: false

Danger · zona berbahaya
Hapus akun Studio

Semua prospek, klien, dan invoice ikut terhapus. Ekspor datanya dulu.

Ringan · kepala berupa snippet
Klinik Gigi Senyum ok 17 Okt · 1,2 dtk

Jawaban model yang sudah disamarkan tampil di sini.

CV Bangun Jaya gangguan

Batas waktu tercapai; pengunjung mendapat pesan ramah.

Pakai

svelte
<Collapsible judul="Karya baru" ikon={Plus} bind:open={terbuka}>
	<form method="POST" action="?/tambah">…</form>
</Collapsible>

<Collapsible variant="ringan">
	{#snippet kepala()}Balasan <Badge>2</Badge>{/snippet}
	…
</Collapsible>

Aksesibilitas & keyboard

  • <details>/<summary> asli: Enter atau Spasi membuka, bekerja tanpa JavaScript.
  • Ringkasan memakai cincin fokus yang sama dengan kontrol lain.
  • Isi yang tertutup tetap ditemukan pencarian Ctrl+F peramban.

combobox

mandiri Formulir

Select yang bisa diketik untuk menyaring daftar panjang.

ts
import Combobox from '#lib/ui/combobox.svelte';
Select yang bisa dicari

nilai: gresik

Galat & nonaktif

Pilih salah satu kota.

Pakai

svelte
<Field label="Kota klien">
	<Combobox items={kota} bind:value name="kota" placeholder="Ketik nama kota…" />
</Field>

Aksesibilitas & keyboard

  • Ketik untuk menyaring; panah atas/bawah memilih, Enter menetapkan, Esc menutup.
  • Di dalam Field, id/aria-describedby/aria-invalid terpasang otomatis.
  • Daftar pendek (< 8 opsi)? Select atau RadioGroup lebih cepat dipindai.

command-dialog

komposisi Overlay

Palet perintah dalam dialog: cari, panah, Enter; opsional ⌘K.

ts
import CommandDialog from '#lib/ui/command-dialog.svelte';
Palet perintah

Pakai

svelte
<CommandDialog bind:open groups={[{ heading: 'Halaman', items: [{ label: 'Invoice', keywords: ['tagihan'], onSelect: () => goto('/studio/invoices') }] }]} />
<!-- hotkey: ⌘K / Ctrl+K membuka dari mana saja — pasang SATU per halaman -->
<CommandDialog hotkey groups={…} />

Aksesibilitas & keyboard

  • Fokus langsung ke kotak cari; panah atas/bawah memilih, Enter menjalankan, Esc menutup.
  • keywords membuat "tagihan" menemukan "Invoice".
  • Demo ini tanpa hotkey supaya tidak bertabrakan dengan palet perintah situs yang sudah aktif.

context-menu

mandiri Overlay

Menu klik kanan (tekan lama di layar sentuh) dari daftar aksi.

ts
import ContextMenu from '#lib/ui/context-menu.svelte';
Menu klik kanan
Klik kanan di sini (tekan lama di ponsel)

Pakai

svelte
<ContextMenu items={aksi} class="block">
	<BarisInvoice {invoice} />
</ContextMenu>

Aksesibilitas & keyboard

  • Klik kanan, tekan lama di layar sentuh, atau Shift+F10 / tombol Menu saat pemicu difokus.
  • Klik kanan tidak terlihat: setiap aksinya harus juga tersedia lewat tombol biasa.
  • Baris menu sama dengan Menu (MenuEntry) — item, label, pemisah, submenu.

date-picker

mandiri Formulir

Tanggal yang bisa diketik per bagian atau dipilih di kalender.

ts
import DatePicker from '#lib/ui/date-picker.svelte';
Ketik atau pilih
01112026

nilai: 2026-11-01

Kosong & nonaktif
hhbbtttt
20102026

Pakai

svelte
<DatePicker bind:value={jatuhTempo} name="due_date" label="Jatuh tempo" />

Aksesibilitas & keyboard

  • Tiap bagian (hari/bulan/tahun) bisa diketik atau dinaik-turunkan dengan panah.
  • Tombol kalender berlabel "Buka kalender"; kalender mengikuti keyboard Calendar.
  • Dengan name, nilai ISO ikut terkirim lewat input tersembunyi — sama dengan kolom date Postgres.

dialog

mandiri Overlay

Jendela modal dengan judul, isi, dan baris tombol.

ts
import Dialog from '#lib/ui/dialog.svelte';
Form di dalam dialog
Informasi saja

Pakai

svelte
<Dialog bind:open title="Ubah data klien" description="…">
	{#snippet trigger(props)}<Button {...props} variant="outline">Ubah</Button>{/snippet}
	<Field label="Nama"><Input /></Field>
	{#snippet footer()}
		<Button variant="outline" onclick={() => (open = false)}>Batal</Button>
		<Button onclick={simpan}>Simpan</Button>
	{/snippet}
</Dialog>

Aksesibilitas & keyboard

  • Fokus terkunci di dalam dialog; Esc atau tombol × (berlabel "Tutup") menutup.
  • Saat tertutup, fokus kembali ke pemicunya.
  • Judul dan deskripsi tersambung lewat aria-labelledby/aria-describedby.
  • Isi panjang menggulir di dalam dialog, tidak di halaman belakangnya.

empty

mandiri Umpan balik

Keadaan kosong yang menjelaskan dan menawarkan langkah berikutnya.

ts
import Empty from '#lib/ui/empty.svelte';
md · satu halaman

Belum ada prospek

Pesan dari formulir kontak akan muncul di sini.

sm · di dalam daftar

Tidak ada yang cocok

Coba kata lain atau hapus saringan.

Pakai

svelte
<Empty icon={Inbox} title="Belum ada prospek" description="Pesan dari formulir kontak muncul di sini.">
	{#snippet action()}<Button size="sm" variant="outline">Bagikan tautan kontak</Button>{/snippet}
</Empty>
<Empty ukuran="sm" title="Tidak ada hasil" />

Aksesibilitas & keyboard

  • Judulnya teks biasa (<p>), bukan heading — tidak mengganggu kerangka halaman.
  • Ikon hanya hiasan (aria-hidden); jelaskan keadaannya dengan kata-kata.
  • Sertakan langkah berikutnya bila ada: tombol, tautan, atau saran.

field

komposisi Formulir

Label, petunjuk, dan galat yang tersambung otomatis ke kontrol di dalamnya.

ts
import Field from '#lib/ui/field.svelte';
Label, petunjuk, wajib

Seperti yang tertulis di papan nama.

Galat & nonaktif

Alamat email belum lengkap — kurang domain.

Inline · kontrol di kiri

Kabar setiap tahap proyek selesai.

Pakai

svelte
<Field label="Email" hint="Untuk balasan." error={galat.email} required teksWajib={m.wajib()}>
	<Input type="email" name="email" />
</Field>

Aksesibilitas & keyboard

  • Kontrol di dalamnya mendapat id, aria-describedby (galat + petunjuk), dan aria-invalid otomatis.
  • Galat yang muncul setelah validasi langsung tersambung — konteksnya berupa getter.
  • Tanda * disembunyikan; pembaca layar mendengar teksWajib ("(wajib)" atau versi bahasanya).
  • inline untuk Checkbox/Switch: kontrol di kiri, label dan petunjuk di kanan.

grafik

mandiri Grafik

Grafik batang atau garis SVG murni, dengan tabel tersembunyi untuk pembaca layar.

ts
import Grafik from '#lib/ui/grafik.svelte';
Batang
Prospek per bulan
Prospek per bulan
bProspek
Mei3
Jun5
Jul2
Agu8
Sep6
Okt9
Batang berkelompok · isi dan garis tepi
Uang per bulan Dibayar Belum dibayar
Uang per bulan
bDibayarBelum dibayar
MeiRp 4,5 jtRp 0 rb
JunRp 0 rbRp 3 jt
JulRp 7,5 jtRp 0 rb
AguRp 3 jtRp 4,5 jt
SepRp 12 jtRp 0 rb
OktRp 6 jtRp 2,5 jt
Garis dengan area
Pendapatan kumulatif
Pendapatan kumulatif
bTotal dibayar
MeiRp 4,5 jt
JunRp 4,5 jt
JulRp 12 jt
AguRp 15 jt
SepRp 27 jt
OktRp 33 jt

Pakai

svelte
<Grafik
	judul="Prospek per bulan"
	data={bulanan}
	sumbuX="b"
	seri={[{ kunci: 'prospek', label: 'Prospek', warna: 'var(--accent)' }]}
/>

Aksesibilitas & keyboard

  • Gambar ber-role="img" dengan judul; datanya juga tersedia sebagai tabel tersembunyi.
  • Sumbu Y selalu angka bulat; bulan bernilai nol tetap tampil.
  • Seri "belum terjadi" memakai garis tepi (kosong), bukan hanya warna berbeda.

hover-card

mandiri Overlay

Pratinjau pelengkap saat tautan disorot; tidak untuk isi penting.

ts
import HoverCard from '#lib/ui/hover-card.svelte';
Pratinjau tautan

Proyek ini dikerjakan untuk — sorot atau fokus namanya.

Pakai

svelte
<HoverCard>
	{#snippet trigger(props)}<a {...props} href="/projects/qomunal">Qomunal</a>{/snippet}
	<p class="font-bold">PT Qomunal Media Pratama</p>
</HoverCard>

Aksesibilitas & keyboard

  • Muncul setelah 300 ms saat disorot atau difokus; Esc menutup.
  • Hanya pelengkap: di layar sentuh kartu ini tidak muncul, jadi isi penting jangan di sini.
  • Pemicunya tetap tautan biasa yang berfungsi tanpa kartu.

input

mandiri Formulir

Kotak isian satu baris yang meneruskan semua atribut <input>.

ts
import Input from '#lib/ui/input.svelte';
Teks & bind:value

Dipakai di salam pembuka balasan.

nilai: —

Jenis lain

Kalender bawaan peramban — tanpa pustaka tambahan.

Pakai

svelte
<Field label="Nama">
	<Input name="nama" bind:value={nama} autocomplete="name" />
</Field>

Aksesibilitas & keyboard

  • Selalu punya label: bungkus dengan Field, atau beri aria-label bila labelnya terlihat di tempat lain.
  • type yang tepat (email, tel, url, date) memunculkan papan ketik ponsel yang sesuai.
  • Galat ditandai garis merah DAN teks galat dari Field — bukan warna saja.

input-group

komposisi Formulir

Input dengan ikon di awal dan awalan/akhiran tetap seperti "Rp" atau ".id".

ts
import InputGroup from '#lib/ui/input-group.svelte';
Awalan & akhiran

Perkiraan saja, boleh dikosongkan.

Ikon di awal

Nama akun hanya huruf, angka, titik, dan garis bawah.

Pakai

svelte
<Field label="Anggaran">
	<InputGroup prefix="Rp" suffix=",-" inputmode="numeric" name="anggaran" />
</Field>
<InputGroup ikon={Search} type="search" aria-label="Cari prospek" />

Aksesibilitas & keyboard

  • Awalan, akhiran, dan ikon disembunyikan dari pembaca layar — sebut satuannya di label bila penting.
  • Seluruh kotak memakai cincin fokus saat input di dalamnya difokus.
  • Ikon hanya hiasan: pencarian tetap butuh label (Field atau aria-label).

item

mandiri Tata letak

Baris berbingkai: media di awal, judul dan deskripsi, aksi di akhir.

ts
import Item from '#lib/ui/item.svelte';
Media, teks, aksi

Company profile

Lima halaman, formulir kontak, selesai 3 minggu.

INV/2026/014

Toko Kue Bu Sari · jatuh tempo 1 Nov

Segera

Pakai

svelte
<Item judul="Company profile" deskripsi="5 halaman · selesai 3 minggu">
	{#snippet awal()}<Globe class="size-5" aria-hidden="true" />{/snippet}
	{#snippet aksi()}<Button size="sm" variant="outline">Pilih</Button>{/snippet}
</Item>

Aksesibilitas & keyboard

  • Judul berupa teks tebal, bukan heading; pakai Card bila butuh struktur dokumen.
  • Aksi di akhir membungkus ke baris baru di layar sempit.

kbd

mandiri Data

Penanda tombol papan ketik seperti ⌘ atau K.

ts
import Kbd from '#lib/ui/kbd.svelte';
Pintasan

Palet perintah: ⌘K atau CtrlK

Kirim: Enter · baris baru: ⇧Enter

Pakai

svelte
<Kbd>⌘</Kbd><Kbd>K</Kbd>

Aksesibilitas & keyboard

  • Elemen <kbd> asli; simbol seperti ⌘ dibacakan apa adanya — tulis juga "Ctrl" untuk Windows.
  • Hanya penanda: pintasannya sendiri dipasang di komponen yang menanganinya.

kisi

mandiri Tata letak

Kisi editorial yang selnya berbagi satu garis 2 px.

ts
import Kisi from '#lib/ui/kisi.svelte';
Daftar berurutan · 1 → 2 → 4 kolom
  1. 01

    Kenalan

    1 hari

  2. 02

    Rancang

    3–5 hari

  3. 03

    Bangun

    1–3 minggu

  4. 04

    Serah terima

    1 hari

Kisi angka · Stat ukuran sm

Prospek baru

4

Proyek aktif

1

Belum dibayar

9,8 jt

Lunas

8 jt

Pakai

svelte
<Kisi elemen="ol" kolom={{ sm: 2, lg: 4 }}>
	{#each tahap as t (t.id)}
		<KisiSel elemen="li">…</KisiSel>
	{/each}
</Kisi>

Aksesibilitas & keyboard

  • Pilih elemen yang jujur: ol/ul untuk urutan atau daftar (sel memakai elemen="li"), div untuk tata letak.
  • Garis dibentuk celah 1 px di atas latar garis — isi sel harus berlatar (KisiSel sudah bg-bg).
  • Penuhi baris terakhir; slot kosong tampil sewarna garis.

kisi-sel

mandiri Tata letak

Satu sel Kisi; dengan href seluruh sel menjadi tautan.

ts
import KisiSel from '#lib/ui/kisi-sel.svelte';

Pakai

svelte
<KisiSel href={l('/services')}>
	<h3 class="display text-3xl">Company profile</h3>
	<p class="text-muted">…</p>
</KisiSel>

Aksesibilitas & keyboard

  • Dengan href, seluruh sel adalah SATU tautan — jangan taruh tautan atau tombol lain di dalamnya.
  • Cincin fokus digambar di dalam sel (offset negatif) supaya tidak tertutup tetangga.
  • Isi tautan dibaca sebagai nama tautan: buat ringkas, judul lebih dulu.

label

mandiri Formulir

Label kontrol mandiri dengan tanda wajib, untuk kontrol di luar Field.

ts
import Label from '#lib/ui/label.svelte';
Bawaan · dengan tanda wajib
Eyebrow · bilah alat ringkas

Pakai

svelte
<Label for="kota" wajib>Kota</Label>
<Input id="kota" name="kota" required />

<Label for="urut" variant="eyebrow">Urutkan</Label>

Aksesibilitas & keyboard

  • for harus sama dengan id kontrol — klik label memfokuskan kontrolnya.
  • wajib menampilkan * yang tersembunyi dari pembaca layar, plus teks "(wajib)".
  • Untuk form biasa pakai Field: label, petunjuk, dan galat tersambung sekaligus.

menu

mandiri Overlay

Menu tarik-turun dari data: item, centang, radio, label, dan submenu.

ts
import Menu from '#lib/ui/menu.svelte';
import type { MenuEntry } from '#lib/ui/menu-types';
Aksi · ikon, pintasan, submenu, bahaya
Centang & radio

jatuh tempo: tampil · urut: terbaru

Pakai

svelte
<Menu items={aksi} align="end">
	{#snippet trigger(props)}
		<Button {...props} variant="outline" size="icon" aria-label="Aksi invoice"><MoreHorizontal /></Button>
	{/snippet}
</Menu>

Aksesibilitas & keyboard

  • Enter/Spasi/panah bawah membuka; panah berpindah, huruf melompat ke item, Esc menutup.
  • Submenu dibuka dengan panah kanan, ditutup dengan panah kiri.
  • Item centang dan radio mengumumkan keadaannya (menuitemcheckbox/menuitemradio).
  • Pemicu ikon saja wajib aria-label.

menubar

mandiri Navigasi

Bilah menu bergaya aplikasi desktop dengan navigasi panah.

ts
import Menubar from '#lib/ui/menubar.svelte';
Bilah menu

Pilih menu dengan mouse atau tombol panah.

Pakai

svelte
<Menubar
	label="Editor"
	menus={[{ id: 'berkas', nama: 'Berkas', items: [{ id: 'simpan', nama: 'Simpan', aksi: simpan }] }]}
/>

Aksesibilitas & keyboard

  • Panah kiri/kanan berpindah menu, panah bawah membuka, Enter memilih, Esc menutup.
  • Hanya untuk alat bergaya aplikasi desktop; navigasi situs memakai NavigationMenu.
  • API-nya berbeda dari Menu (bukan MenuEntry): tiap item cukup id, nama, aksi.

native-select

mandiri Formulir

<select> bawaan peramban bergaya kontrol, ringan dan ramah ponsel.

ts
import NativeSelect from '#lib/ui/native-select.svelte';
Dengan optgroup

Bekerja juga saat JavaScript belum dimuat.

nilai: kamar

Nonaktif

Pakai

svelte
<Field label="Urutkan">
	<NativeSelect name="urut" bind:value={urut}>
		<option value="baru">Terbaru</option>
		<option value="nominal">Nominal</option>
	</NativeSelect>
</Field>

Aksesibilitas & keyboard

  • Elemen <select> asli: pemilih sistem di ponsel, huruf awal melompat ke opsi, tanpa JavaScript.
  • Chrome/Edge 135+ membuka daftar bergaya Select (base-select) tanpa JavaScript; peramban lain memakai pemilih sistem.
  • Di dalam Field, id dan aria-describedby terpasang otomatis.
  • Butuh grup bergaya atau pencarian? Select atau Combobox.

nav-list

komposisi Navigasi

Navigasi vertikal kolom samping dengan ikon, badge, dan halaman aktif.

ts
import NavList from '#lib/ui/nav-list.svelte';

Pakai

svelte
<NavList
	label="Kerja"
	active={halaman}
	items={[{ key: 'leads', label: 'Prospek', href: resolve('/studio/leads'), icon: Inbox, badge: 4 }]}
/>

Aksesibilitas & keyboard

  • Dibungkus <nav> berlabel; halaman aktif bertanda aria-current="page" dan garis aksen.
  • Badge angka ikut dibacakan setelah labelnya ("Prospek 4").
  • Tinggi baris 36 px — nyaman untuk sentuh.

navigation-menu

mandiri Navigasi

Deret tautan mendatar dengan penanda halaman aktif, tanpa panel tarik-turun.

ts
import NavigationMenu from '#lib/ui/navigation-menu.svelte';

Pakai

svelte
<NavigationMenu label="Bagian halaman" items={[{ label: 'Ringkasan', href: '#ringkasan', aktif: true }]} />

Aksesibilitas & keyboard

  • Dibungkus <nav> berlabel; tautan aktif bertanda aria-current="page".
  • Catatan: berbeda dari Navigation Menu shadcn — tidak ada panel tarik-turun, hanya deret tautan.
  • Tautan biasa: Tab berpindah, Enter mengikuti.

pagination

mandiri Navigasi

Penomoran halaman yang selalu menampilkan halaman pertama, terakhir, dan tetangga.

ts
import Pagination from '#lib/ui/pagination.svelte';
120 baris · 10 per halaman

halaman: 4

Pakai

svelte
<Pagination bind:page count={total} perPage={20} />

Aksesibilitas & keyboard

  • Dibungkus <nav aria-label="Halaman">; halaman aktif ditandai dan diumumkan.
  • Tombol sebelum/berikutnya berlabel teks dan nonaktif di ujung.
  • Halaman pertama, terakhir, dan tetangga selalu tampil; sisanya "…" yang disembunyikan.

pin-input

mandiri Formulir

Kotak per angka untuk kode sekali pakai, di atas satu <input> asli.

ts
import PinInput from '#lib/ui/pin-input.svelte';
Kode sekali pakai

Masukkan 6 angka dari aplikasi autentikator.

Tempel 6 angka sekaligus juga bisa.

Nonaktif · 4 angka
1
2

Pakai

svelte
<PinInput name="kode" label="Kode sekali pakai" bind:value={kode} onComplete={verifikasi} />

Aksesibilitas & keyboard

  • Satu <input> asli di belakangnya: tempel 6 angka, isi otomatis dari SMS/pengelola sandi, dan form tanpa JS tetap jalan.
  • inputmode="numeric" + autocomplete="one-time-code" memunculkan papan angka di ponsel.
  • Kotak-kotak tidak menjelaskan diri sendiri: taruh di dalam Field (label, petunjuk, galat tersambung otomatis) atau beri prop label.

popover

mandiri Overlay

Panel kecil yang melayang di dekat pemicunya.

ts
import Popover from '#lib/ui/popover.svelte';
Panel berisi kontrol

Pakai

svelte
<Popover bind:open align="end">
	{#snippet trigger(props)}<Button {...props} variant="outline">Bagikan</Button>{/snippet}
	Isi interaktif…
</Popover>

Aksesibilitas & keyboard

  • Pemicu mengumumkan aria-expanded; fokus pindah ke panel saat terbuka.
  • Esc atau klik di luar menutup, lalu fokus kembali ke pemicu.
  • Boleh berisi kontrol (berbeda dari Tooltip yang hanya teks).

progress

mandiri Umpan balik

Bilah kemajuan 0–100, atau tak tentu bila nilainya belum diketahui.

ts
import Progress from '#lib/ui/progress.svelte';
Tertentu · digerakkan tombol
35%
Tak tentu

Pakai

svelte
<Progress value={persen} label="Unggah berkas" />
<Progress value={null} label="Memproses" />

Aksesibilitas & keyboard

  • role="progressbar" dengan aria-valuenow; label wajib menjelaskan apa yang sedang berjalan.
  • value null = tak tentu (berdenyut pelan); nilai di luar 0–100 dijepit, bukan diabaikan.
  • Denyut berhenti dengan prefers-reduced-motion.

radio-group

mandiri Formulir

Satu pilihan dari beberapa opsi berlabel dengan petunjuk opsional.

ts
import RadioGroup from '#lib/ui/radio-group.svelte';
Dengan petunjuk & opsi nonaktif

Termasuk dalam paket.

+20% dari harga paket.

+45% dari harga paket.

nilai: rapi

Pakai

svelte
<RadioGroup name="desain" bind:value aria-label="Tingkat desain" items={[{ value: 'rapi', label: 'Rapi', hint: 'Termasuk paket.' }]} />

Aksesibilitas & keyboard

  • Satu titik Tab untuk seluruh grup; panah berpindah dan langsung memilih.
  • Grup wajib berlabel (aria-label atau aria-labelledby).
  • id tiap opsi diturunkan dari $props.id() — dua grup bernilai sama tidak bertabrakan.

resizable

mandiri Tata letak

Dua panel yang lebarnya dibagi lewat kontrol rentang.

ts
import Resizable from '#lib/ui/resizable.svelte';
Dua panel

Daftar

INV/2026/014, INV/2026/013, INV/2026/012.

Pratinjau

Geser kontrol di atas untuk membagi ruang.

Pakai

svelte
<Resizable label="Lebar daftar" bind:value={lebar}>
	{#snippet awal()}Daftar…{/snippet}
	{#snippet akhir()}Pratinjau…{/snippet}
</Resizable>

Aksesibilitas & keyboard

  • Bukan pegangan seret: Slider (role slider) — panah, Home, End, pointer, dan sentuh.
  • Nilai dijepit 25–75% supaya tidak ada panel yang hilang.
  • Label tampak menyebut persentase; pembaca layar mendengar nilai dari slidernya.

scroll-area

mandiri Tata letak

Area bertinggi tetap yang bisa digulir, termasuk dengan keyboard.

ts
import ScrollArea from '#lib/ui/scroll-area.svelte';
Tinggi tetap
  1. 09:00Prospek baru masuk
  2. 09:04Invoice dikirim
  3. 09:08Balasan terkirim
  4. 09:12Prospek baru masuk
  5. 09:16Invoice dikirim
  6. 09:20Balasan terkirim
  7. 09:24Prospek baru masuk
  8. 09:28Invoice dikirim
  9. 09:32Balasan terkirim
  10. 09:36Prospek baru masuk
  11. 09:40Invoice dikirim
  12. 09:44Balasan terkirim
  13. 09:48Prospek baru masuk
  14. 09:52Invoice dikirim

Pakai

svelte
<ScrollArea label="Riwayat aktivitas" tinggi="12rem">…</ScrollArea>

Aksesibilitas & keyboard

  • Bisa difokus (Tab) lalu digulir dengan panah/PageDown — tanpa itu pengguna keyboard terkunci.
  • Berlabel (section aria-label) supaya fokusnya punya nama.
  • overscroll-contain: gulir di ujung tidak ikut menggulir halaman.

select

mandiri Formulir

Pilihan bergaya dengan grup dan opsi nonaktif; tetap terkirim lewat form.

ts
import Select, { type SelectOption } from '#lib/ui/select.svelte';
Grup & opsi nonaktif

nilai: cp

Belum dipilih · galat

Pilih satu paket untuk lanjut.

Pakai

svelte
<Field label="Paket">
	<Select items={paket} bind:value name="paket" required />
</Field>

Aksesibilitas & keyboard

  • Enter/Spasi/panah membuka; huruf awal melompat ke opsi; Esc menutup.
  • Dengan name, nilai terkirim lewat input tersembunyi — form tanpa JavaScript tetap jalan.
  • Opsi bergrup tampil di bawah judulnya; opsi nonaktif dilewati keyboard.

separator

mandiri Tata letak

Garis pemisah mendatar atau tegak.

ts
import Separator from '#lib/ui/separator.svelte';
Mendatar

Ringkasan proyek

Tahap 3 dari 7 · Company profile

Tegak
Prospek Klien Invoice

Pakai

svelte
<Separator />
<Separator vertikal />

Aksesibilitas & keyboard

  • role="separator" dengan aria-orientation; murni pemisah visual-semantik, tidak bisa difokus.
  • Untuk pemisah hiasan di dalam baris teks, cukup border CSS.

sheet

mandiri Overlay

Panel geser dari kanan, kiri, atau bawah layar.

ts
import Sheet from '#lib/ui/sheet.svelte';
Kanan · rincian
Bawah · saringan ponsel

Pakai

svelte
<Sheet side="bottom" title="Saringan" description="…">
	{#snippet trigger(props)}<Button {...props} variant="outline">Saring</Button>{/snippet}
	Isi panel…
</Sheet>

Aksesibilitas & keyboard

  • Dialog modal: fokus terkunci di dalam, Esc atau × menutup, fokus kembali ke pemicu.
  • hideTitle menyembunyikan judul secara visual — tetap dibacakan.
  • Panel bawah lebih mudah dijangkau jempol di ponsel; geraknya mati dengan reduced motion.

skeleton

mandiri Umpan balik

Kerangka berkilau pengganti isi yang sedang dimuat.

ts
import Skeleton from '#lib/ui/skeleton.svelte';
Baris daftar
Papan game

Pakai

svelte
<div aria-busy="true">
	<span class="sr-only">Memuat papan…</span>
	<Skeleton class="aspect-square w-full max-w-[34rem] border-2 border-border" />
</div>

Aksesibilitas & keyboard

  • aria-hidden: kerangka tidak dibacakan — umumkan "memuat" di pembungkusnya (aria-busy + teks sr-only).
  • Kilau menyapu berhenti dengan prefers-reduced-motion.
  • Samakan ukurannya dengan isi asli supaya tata letak tidak melompat.

slider

mandiri Formulir

Penggeser satu nilai atau rentang dua nilai.

ts
import Slider from '#lib/ui/slider.svelte';
Satu nilai

5 halaman

Rentang

Anggaran: Rp 5 – 15 jt

Pakai

svelte
<Slider label="Jumlah halaman" bind:value={halaman} min={1} max={12} name="halaman" />

Aksesibilitas & keyboard

  • Panah menggeser satu langkah, PageUp/PageDown sepuluh, Home/End ke ujung.
  • Tiap pegangan berlabel; rentang dua nilai menjadi "Anggaran 1" dan "Anggaran 2".
  • Tampilkan nilainya sebagai teks di dekat slider — posisi pegangan saja tidak cukup.

sortable-th

mandiri Data

Kepala kolom tabel yang bisa diurutkan, dengan aria-sort di <th>.

ts
import SortableTh, { type SortDir } from '#lib/ui/sortable-th.svelte';
Naik → turun → asal
Klik kepala kolom Proyek untuk mengurutkan.
Klien
Toko Kue Bu Sari2
Klinik Gigi Senyum1
CV Bangun Jaya3

Pakai

svelte
<thead><tr>
	<SortableTh label="Total" align="right" dir={kunci === 'total' ? arah : null} onSort={() => urutkan('total')} />
</tr></thead>

Aksesibilitas & keyboard

  • aria-sort dipasang di <th> (ascending/descending/none), bukan di tombolnya.
  • Tombolnya bisa difokus; Enter/Spasi mengurutkan. Ikon panah hanya hiasan.
  • Pemanggil yang menyimpan arah — komponen ini tidak mengurutkan data.

spinner

mandiri Umpan balik

Penanda memuat kecil yang ikut dibacakan pembaca layar.

ts
import Spinner from '#lib/ui/spinner.svelte';
Sebaris teks
Memuat Mengambil data proyek…
Di tombol

Pakai

svelte
<Spinner label="Mengambil data proyek" />
<Button loading>Simpan</Button> <!-- Button memakai Spinner sendiri -->

Aksesibilitas & keyboard

  • role="status" dengan teks sr-only (bawaan "Memuat") — tulis label yang spesifik.
  • Putaran berhenti dengan prefers-reduced-motion; teks di sebelahnya tetap menjelaskan.
  • Di tombol, pakai prop loading — jangan menaruh Spinner sendiri.

stat

mandiri Data

Satu angka berlabel dengan perubahan naik/turun opsional.

ts
import Stat from '#lib/ui/stat.svelte';
Ukuran md (bawaan)

Prospek baru

4

2 dari minggu lalu

Proyek aktif

1

Belum dibayar

9,8 jt

1 terlambat

Lunas bulan ini

8 jt

2 jt dari Sept

sm · kisi rapat

Main

128

Selesai

97

Rata-rata

4:12

lg · angka utama

Pendapatan 2026

33 jt

12% dari 2025

Pakai

svelte
<Stat label="Belum dibayar" value="9,8 jt" delta="1 terlambat" trend="down" ukuran="sm" />

Aksesibilitas & keyboard

  • Label dibaca sebelum angka; angka memakai tabular-nums supaya kolom sejajar.
  • Arah tren ditulis dalam teks delta — panah ↑/↓ hanya hiasan.

switch

mandiri Formulir

Sakelar nyala/mati untuk pengaturan yang langsung berlaku.

ts
import Switch from '#lib/ui/switch.svelte';
Keadaan

Kabar setiap tahap selesai.

notifikasi: nyala

Pakai

svelte
<Field label="Notifikasi email" inline>
	<Switch name="notifikasi" bind:checked={notifikasi} />
</Field>

Aksesibilitas & keyboard

  • role="switch" dengan aria-checked; Spasi/Enter mengganti keadaan.
  • Untuk pengaturan yang berlaku seketika; pilihan yang baru berlaku saat form dikirim lebih jujur sebagai Checkbox.
  • Tinggi 24 px — memenuhi area sentuh minimum.

table

mandiri Data

Pembungkus tabel yang menggulir di dalam bingkainya sendiri.

ts
import Table from '#lib/ui/table.svelte';
Resep data table · Table + SortableTh + Pagination + Badge
Lima invoice terakhir; klik Klien atau Total untuk mengurutkan.
NomorStatus
INV/2026/012CV Bangun JayaLunasRp 8.000.000
INV/2026/014Toko Kue Bu SariTerlambatRp 6.500.000
INV/2026/010Bengkel Maju MotorLunasRp 4.200.000

Pakai

svelte
<Table caption="Invoice terakhir.">
	<thead><tr><th scope="col">Nomor</th><th scope="col" class="num">Total</th></tr></thead>
	<tbody>
		{#each baris as r (r.no)}<tr><td>{r.no}</td><td class="num">{rupiah(r.total)}</td></tr>{/each}
	</tbody>
</Table>

Aksesibilitas & keyboard

  • <table> asli dengan caption dan th scope — struktur yang dibaca pembaca layar baris demi baris.
  • Menggulir di dalam bingkainya sendiri (gulir-x): halaman tidak ikut bergeser di ponsel.
  • Sel angka: kelas num (rata kanan, mono, tabular).

tabs

mandiri Navigasi

Tab bergaris bawah tebal untuk berpindah panel di halaman yang sama.

ts
import Tabs from '#lib/ui/tabs.svelte';
Tiga panel

Proyek company profile, tahap 3 dari 7.

Pakai

svelte
<Tabs bind:value items={[{ value: 'a', label: 'Ringkasan' }]}>
	{#snippet panel(v)}…{/snippet}
</Tabs>

Aksesibilitas & keyboard

  • Satu titik Tab untuk daftar tab; panah kiri/kanan berpindah, Home/End ke ujung.
  • Panel aktif bisa difokus (Tab berikutnya) dan berlabel tabnya.
  • Daftar tab menggulir mendatar tanpa batang gulir di layar sempit.

textarea

mandiri Formulir

Isian banyak baris yang tingginya mengikuti isi.

ts
import Textarea from '#lib/ui/textarea.svelte';
Tumbuh mengikuti isi

0 / 500 karakter

Galat & nonaktif

Alamat wajib diisi untuk invoice.

Pakai

svelte
<Field label="Ceritakan kebutuhannya">
	<Textarea name="pesan" bind:value maxlength={2000} />
</Field>

Aksesibilitas & keyboard

  • Tingginya mengikuti isi (field-sizing: content) — tanpa JavaScript dan tanpa gulir ganda.
  • Batas panjang lewat maxlength; tampilkan sisa karakter sebagai teks.

toaster

mandiri Umpan balik

Kabar sesaat di pojok layar, dipanggil lewat toast().

ts
import { toast } from '#lib/ui/toast.svelte.ts';
// <Toaster /> sudah dipasang SEKALI di layout root.
Empat bentuk

Pakai

svelte
toast({ title: 'Pesan terkirim', description: 'Dibalas dalam 24 jam kerja.', variant: 'success' });
toast({ title: 'Prospek diarsipkan', action: { label: 'Urungkan', onClick: kembalikan } });

Aksesibilitas & keyboard

  • Live region aria-live="polite" selalu ada, jadi kabar baru selalu diumumkan.
  • Galat memakai role="alert"; kabar lain role="status".
  • Hilang sendiri setelah 5 detik — jangan taruh satu-satunya jalan ke tindakan penting di toast.
  • toast() tidak berjalan di server: kabar satu pengunjung tidak bocor ke pengunjung lain.

toggle

mandiri Aksi

Tombol dua keadaan dengan aria-pressed.

ts
import Toggle from '#lib/ui/toggle.svelte';
Teks

pressed: false

Ikon saja · dengan label

Pakai

svelte
<Toggle bind:pressed={arsip}>Tampilkan arsip</Toggle>
<!-- Ikon saja: baru di sini label dipakai -->
<Toggle bind:pressed={tebal} label="Tebal"><Bold /></Toggle>

Aksesibilitas & keyboard

  • aria-pressed mengumumkan keadaan; Spasi/Enter mengganti.
  • Nama tombol berasal dari teks yang terlihat; label hanya untuk isi ikon saja (WCAG 2.5.3).
  • Teks tidak berubah saat ditekan — keadaannya dibawa aria-pressed, bukan label yang berganti.

toggle-group

mandiri Aksi

Kontrol segmen satu pilihan seperti IDR/USD.

ts
import ToggleGroup from '#lib/ui/toggle-group.svelte';
Dua pilihan

nilai: idr

Tiga pilihan

Pakai

svelte
<ToggleGroup label="Mata uang" bind:value items={[{ value: 'idr', label: 'IDR' }, { value: 'usd', label: 'USD' }]} />

Aksesibilitas & keyboard

  • Satu titik Tab; panah kiri/kanan berpindah, Spasi/Enter memilih.
  • Grup berlabel; pilihan aktif diumumkan sebagai tertekan.
  • Segmen tidak menyusut (label satu baris); di wadah sempit grup menggulir mendatar sendiri — tidak perlu pembungkus gulir-x, cukup min-w-0 pada wadah flex/grid — dan cincin fokus digambar di dalam segmen.
  • Butuh JavaScript. Untuk form tanpa JS (mis. tema), pakai ButtonGroup menempel + Button type="submit".

tooltip

mandiri Overlay

Keterangan satu baris saat kontrol disorot atau difokus.

ts
import Tooltip from '#lib/ui/tooltip.svelte';
Pada tombol ikon

Pakai

svelte
<Tooltip content="Salin ke papan klip">
	{#snippet children(props)}
		<Button {...props} variant="ghost" size="icon" aria-label="Salin"><Copy /></Button>
	{/snippet}
</Tooltip>

Aksesibilitas & keyboard

  • Muncul saat disorot ATAU difokus keyboard (250 ms); Esc menyembunyikan.
  • Hanya keterangan tambahan: di layar sentuh ia tidak pernah muncul.
  • Tombol ikon tetap butuh aria-label sendiri — tooltip bukan nama aksesibel.
  • Membawa Provider-nya sendiri; tidak perlu pembungkus global.

Komponen fitur · AI

Tinggal di features/ai, bukan lib/ui. Prompt dipakai asisten beranda dengan server nyata; di halaman ini keduanya berjalan tanpa model dan tanpa permintaan jaringan.

prompt

fitur AI

Kotak satu pesan dengan penghitung karakter; dipakai asisten beranda dengan server nyata.

ts
import Prompt from '#lib/features/ai/prompt.svelte';
Demo lokal · tanpa model

Pakai

svelte
<Prompt label="Tanya tentang layanan" bind:value sibuk={memuat} onkirim={tanya} maks={500} />

Aksesibilitas & keyboard

  • Enter mengirim, Shift+Enter baris baru; komposisi IME (Jepang/Mandarin) tidak ikut terkirim.
  • Saat sibuk, isian menjadi readonly dan tombol memakai aria-busy.
  • Komponen tidak tahu soal model — pemanggil yang mengirim dan menampilkan hasil.

jejak

fitur AI

Daftar kejadian proses yang benar-benar terjadi — bukan narasi pikiran model. Belum dipakai di halaman nyata.

ts
import Jejak from '#lib/features/ai/jejak.svelte';
Contoh langkah · digerakkan tombol
  1. Menerima masukanberjalan
  2. Memeriksa batas & kuotamenunggu
  3. Menjawabmenunggu
  4. Menyamarkan data pribadimenunggu

Pakai

svelte
<Jejak label="Status permintaan" langkah={[{ id: 'kirim', judul: 'Mengirim', status: 'selesai' }]} />

Aksesibilitas & keyboard

  • Daftar berurutan (<ol>) berlabel; status ditulis sebagai teks, warna hanya penguat.
  • Pemanggil wajib menjamin status mencerminkan kenyataan — jangan memalsukan "berpikir".

07.3 — Helper

Helper

Bekerja lewat pemanggilnya, tanpa demo sendiri — sudah terpasang di layout root atau dipakai komponen lain.

Komponen helper di lib/ui.
NamaKategoriFungsi
json-ldUtilitasMenulis data terstruktur JSON-LD ke <head> dengan isi yang di-escape.
menu-itemsUtilitasBaris menu bersama yang dirender Menu dan ContextMenu.
progres-navigasiUmpan balikBilah tipis di tepi atas saat pindah halaman berlangsung lama.
transisi-halamanUtilitasPembungkus isi halaman: kerangka saat lambat, naik-memudar saat masuk.