Workspace Alpha
Satu kartu, satu aksi utama, satu pintu keluar. Klik di mana saja → tombol utama; "Detail" tetap miliknya sendiri.
Design System · v3.17.0
Freeday adalah sumber kebenaran token-driven & framework-agnostic. Warna, tipografi, spasi, dan 45 komponen aksesibel yang dirakit jadi layar nyata di Vue, React, atau Blazor. Nol dependency runtime, kontras WCAG AA di terang & gelap.
Referensi lengkap: COMPONENTS.md (semua class + markup) · USAGE.md (kapan pakai yang mana) · satu layar utuh · panduan AI agent.
Adapter tipis: enhancer tetap sumber kebenaran, event fdy-* dijembatani ke reaktivitas framework. Vue, React, dan Blazor kini sama-sama punya komponen typed (10/10 parity): FdyCombo, FdyDatepicker, FdyCfl, FdyChart, FdyTable, FdyModal/FdyDrawer, dst. Tiap stack punya contoh layar faktur yang jalan (lihat examples/).
Baru mau pakai di project sendiri? Panduan langkah-demi-langkah per stack →
Composable useFreeday() untuk hydrate saat mount & update, event lewat v-on. Plus komponen v-model: FdyCombo/FdyDatepicker/FdyCfl.
Hook useFreeday() + komponen controlled typed: FdyCombo/FdyDatepicker/FdyCfl/FdyChart (value/onChange).
RCL Freeday.Blazor (net8.0), komponen native typed <FdyX> dengan @bind, 11/11 parity. Fallback: interop window.FreedayBlazor.
Pilih komponen, atur varian lewat kontrol Freeday (chip & switch), lihat pratinjau langsung, lalu salin markup-nya. Semua kelas berasal dari kit, nol dependency, siap tempel ke Vue / React / Blazor.
Empat aturan yang menjaga sistem tetap konsisten seiring pertumbuhannya.
Komponen hanya menyentuh peran (Tier-2), tak pernah ramp mentah. Re-brand & tema cukup ubah token semantic.
Semua spasi kelipatan 4px lewat skala --space-*. Tak pernah nilai lepas.
Satu aksi primer menonjol; sisanya ghost/secondary. Warna status ≠ warna aksen.
Kontras WCAG AA di terang & gelap, fokus selalu terlihat, HTML native dulu sebelum ARIA.
Peran semantic (Tier-2). Komponen hanya menyentuh peran ini, tak pernah ramp mentah.
Palet primary: data-primary. 18 pilihan. Yang di-redefinisi adalah ramp alias delapan shade, bukan token semantic-nya — jadi pemetaan "shade mana yang jadi fill, hover, wash" ditulis sekali dan sebuah palet tak mungkin berbeda pendapat soal arti "primary". Bisa dipasang di <html> atau di wrapper mana pun untuk me-rebrand satu region. Setiap palet dijaga gerbang kontras di kedua tema; tiga hasilnya tampak seperti kekeliruan kalau tak dijelaskan: yellow memakai fill lebih gelap di tema terang (di shade aslinya cuma 2.94:1 di atas putih) dan karenanya berlabel putih, sedangkan indigo dan violet bergeser satu langkah lebih terang di tema gelap. noir bukan hue sama sekali — ia mengarahkan ramp alias ke ramp netral.
Tekan salah satu, lalu telusuri halaman ini: tombol, tab, fokus, chart, dan setiap -soft wash ikut berubah tanpa satu baris pun disetel per komponen.
Gaya visual: data-style. soft default; glass memfrost permukaan terangkat saja — card, modal, drawer, menu, appbar, sidebar & topbar app shell. Sengaja bukan --color-surface, karena token itu juga jadi isian input, chip, sel tabel dan kolom beku; kolom beku yang tembus pandang akan memperlihatkan baris yang menggulung di bawahnya. Seberapa tembus kaca boleh jadi ditentukan gerbang kontras, bukan selera: di bawah .82 (terang) / .90 (gelap), tinta muted berhenti lolos 4.5:1 begitu panel dikomposit di atas latar sembarang. Jadi frost-nya halus — yang benar-benar terlihat adalah blur-nya, bukan alpha-nya.
Display Sora · body IBM Plex Sans · data JetBrains Mono.
Faktur & laporan yang rapi
Teks isi memakai IBM Plex Sans, nyaman dibaca untuk paragraf, label, dan tabel data bisnis sehari-hari.
INV-1042 · Rp 1.240.000 · 2026-07-30
Skala kelipatan 4px. Selalu var(--space-N), tak pernah nilai lepas.
Sudut & bayangan bertingkat, untuk hierarki kedalaman yang konsisten.
Radius
Elevasi
Skala gerak terbatas & konsisten: tiga durasi (--dur-fast · --dur-base · --dur-slow ) dan dua easing yang keduanya decelerate (tanpa bounce/elastic). Gerak hanya untuk feedback, reveal, dan transisi state, bukan dekorasi. Semua otomatis nonaktif saat prefers-reduced-motion: reduce. Arahkan kursor ke track untuk merasakan easing-nya.
Yang bergerak: Drawer & Modal beranimasi masuk dan keluar (slide / fade-scale + backdrop blur) lewat @starting-style + allow-discrete pada <dialog> native: buka lalu tutup, perhatikan animasi keluarnya. Overlay (menu, select, cascade, autocomplete, date/time picker) reveal saat dibuka; Accordion & Tabs me-reveal konten (fade + rise); baris tabel & kontrol punya transisi hover/press halus.
Gradient gelap-ke-bawah + colored-glow. Satu aksi primer per layar; sisanya ghost.
Text + ikon · text-only (emphasis rendah) · icon-only (butuh aria-label).
Button group (segmented, mendatar atau --stack bertumpuk untuk memilih satu dengan ibu jari) & FAB (bulat · kecil · extended).
Inner-shadow + focus glow. Untuk error, pakai aria-invalid="true". Atribut ini melakukan dua hal sekaligus (state visual dan yang bisa dibaca screen reader), lalu aria-describedby menautkan pesan (help ber-id sama). .fdy-input--error hanya untuk kasus di mana state invalid bukan milik field itu sendiri.
Addon prefix/suffix bebas: teks (Rp / %), ikon dekoratif (__addon--icon), tombol aksi (__btn), atau tanpa addon sama sekali. Jadi "dengan ikon / tanpa ikon / ikon custom" cukup dari markup addon. Field angka = grup yang sama dengan dua tombol step, yaitu data-fdy-number.
Field read-only yang di-backing master data (pelanggan, item, gudang…). Ikon kaca pembesar di kanan membuka dialog pencari; baris yang dipilih mengisi field (kode + nama sekaligus). Nilai selalu dipilih, tak pernah diketik. Multi-pilih: data-fdy-cfl-multiple pada dialog untuk jalur enhancer, atau prop multiple pada wrapper Vue/React (model melebar jadi Row[] | null) dan Multiple + Values pada Blazor. Baris dicentang, footer menghitung, dan Confirm yang meng-commit. Menutup dialog tanpa Confirm tak mengubah nilai sama sekali. Butuh freeday-cfl.js.
Single-select commit saat klik baris (tanpa "OK"). Keyboard: ketik untuk mencari, ↓ masuk daftar, ↑/↓ navigasi, Enter pilih, Esc tutup. Fokus kembali ke tombol pemicu saat ditutup. Baris = server state: di app nyata, jadikan komponen terkontrol (callback fetchPage + cache), bukan store global.
Trigger bergaya input + kalender popover, konsisten lintas-browser & bisa di-theme. (Native <input type="date"> tetap pilihan ringan bila tak butuh theming.) Rentang = dua picker tertaut; akhir tak boleh mendahului awal. Keyboard: ←/→ hari, ↑/↓ minggu, PgUp/PgDn bulan, Enter pilih, Esc tutup. Judul kalender menelusuri dua tingkat: hari → bulan → tahun (12 tahun per halaman, sejajar: 2016–2027, lalu 2028–2039), jadi tanggal lahir tak lagi berarti puluhan klik panah. Tiap tingkat di atas grid hari hanyalah navigasi: tak ada yang di-commit sampai sebuah hari dipilih. Butuh freeday-datepicker.js.
Varian ikon (konvensi kit): default (kalender), tanpa ikon lewat data-fdy-no-icon, atau ikon custom dengan <template data-fdy-icon> di dalam wrapper.
Trigger bergaya input + popup daftar waktu (pola listbox WAI-ARIA). Format 24 jam HH:MM; rentang & langkah diatur lewat data-min/data-max/data-step (default tiap 30 menit). Keyboard: ↓ buka, ↑/↓ pindah, Home/End, Enter pilih, Esc tutup. Butuh freeday-timepicker.js.
Varian ikon (konvensi kit): default (jam), tanpa ikon lewat data-fdy-no-icon, atau ikon custom dengan <template data-fdy-icon>.
Komposisi date picker + time picker jadi satu nilai, memakai ulang dua komponen yang sudah ada, bukan popover monolitik. Composer freeday-datetime.js menyatukan keduanya dan memancarkan satu event fdy-datetime-change (value = YYYY-MM-DDTHH:MM). Varian ikon diwarisi dari tiap picker anak.
Drop target yang juga membuka dialog berkas saat klik / Enter. State eksplisit: idle, drag-over, mengunggah (progress), selesai, error, karena "tidak terjadi apa-apa" adalah feedback unggah terburuk. Butuh freeday-upload.js. (Demo ini mensimulasikan progress; di app nyata, sambungkan event fdy-upload-add ke fungsi unggahmu.)
Dua state terminal (statis): terunggah & ditolak:
Alur multi-langkah: indikator progres (nomor → centang), panel per langkah, tombol Kembali/Lanjut. Langkah yang sudah dilewati bisa diklik untuk ditinjau (linear, tak bisa lompat maju). Butuh freeday-stepper.js.
Langkah bermasalah pakai is-error plus .fdy-step__badge berisi jumlahnya — warna saja tidak membawa pesan apa pun bagi pembaca yang tak bisa melihat merah, jadi jumlah itu disebut juga dalam teks. Meninggalkan langkah bisa ditolak: tombol Lanjut memicu fdy-step-before-change yang bisa dibatalkan, dan validasi yang perlu menunggu server menitipkan promise-nya ke detail.waitFor. Selama promise itu belum selesai, kedua tombol nav dinonaktifkan. Mundur tak pernah ditanya.
Tinjau data, lalu tekan Selesai untuk menyimpan faktur.
Indikator saja (tanpa __btn), dengan langkah kedua bermasalah:
Native <input type="range"> di-tema lewat accent-color, dengan keyboard & ARIA bawaan, fill & thumb otomatis. freeday-slider.js cuma menyalin nilai ke <output>. Warna: primary/accent/danger.
Bintang, memakai grup radio native (panah kiri/kanan untuk memilih, ter-asosiasi form). Terisi vs kosong dibedakan isi vs garis (bukan warna saja). Ada varian read-only. Butuh freeday-rating.js.
Combobox editable: ketik untuk memfilter saran (pola APG combobox + listbox), keyboard ↑/↓/Enter/Esc. Butuh freeday-autocomplete.js. (Opsi statis di-filter di klien; di app nyata, fetch/filter server-side.)
Pemilih hierarki (drill-down); model datanya <ul> bersarang tersembunyi: <li> dengan anak <ul> adalah cabang, tanpa anak adalah daun. Cabang membuka level berikut, daun memilih (nilai = jalur lengkap). Keyboard: ↑/↓ pindah, →/Enter masuk cabang / pilih daun, ←/Backspace naik level, Esc tutup. Butuh freeday-cascade.js.
Field kata sandi dengan tombol tampil/sembunyi (data-fdy-password, memakai kembali chrome tombol input-group) dan input ber-format saat mengetik (data-fdy-mask: # digit, A huruf, * alnum; karakter lain jadi literal otomatis). Butuh freeday-mask.js.
Validasi native (Constraint Validation API) di-wire ke error inline aksesibel: aria-invalid + pesan ter-aria-describedby, fokus ke field invalid pertama saat submit, lalu re-validasi live tiap blur/input. Pesan custom via data-fdy-msg-*, cocokkan antar-field via data-fdy-match. Coba tekan Simpan saat kosong. Butuh freeday-form.js.
Validasi inline
Header + detail (dokumen): .fdy-form-grid + tabel
Baris filter yang konsisten: .fdy-filterbar membungkus beberapa .fdy-field. Tiap field ikut ritme lebar (--w-sm · default · --w-lg · --w-xl); satu field --w-grow (biasanya pencarian) menyerap sisa ruang; aksi (__actions) menempel di ujung, sejajar kontrol. Membungkus rapi di layar sempit. Nol JS, cukup layout di atas .fdy-field. Field pencarian di sini memakai data-fdy-search: mencari saat pengetik berhenti sejenak, langsung saat Enter, dan punya tombol kosongkan. Ketiganya berakhir di satu event fdy-search.
Dropdown berdesain penuh (pola WAI-ARIA APG): popup bisa distyle, keyboard ↑↓/Enter/Esc/ketik. Butuh freeday-select.js. Varian ikon: caret default, tanpa lewat fdy-combo--no-icon, atau custom (--no-icon + .fdy-combo__icon di dalam tombol).
Native <input> distyle, dengan Space, asosiasi label, :focus-visible tetap native. Switch yang labelnya tidak menjelaskan arti "on" diberi hint state: dua baris [data-fdy-when="on|off"] di satu .fdy-help, kit menampilkan yang cocok dengan state sekarang, tanpa JS. Balik switch "Persetujuan" untuk melihatnya.
Pesan inline. Varian danger memakai role="alert".
Notifikasi sekilas di live-region. Panggil Freeday.toast({variant,title,message}) mengembalikan elemen toast. Beri key agar toast dengan key sama menggantikan yang lama (bukan menumpuk saat error yang sama meletup beruntun); tutup lebih awal dengan Freeday.dismiss(node|key).
Muncul saat hover/focus. Asosiasi via aria-describedby.
Pola APG: panah ←/→, Home/End, roving tabindex. Butuh freeday-tabs.js.
Tabel semantik statis: <caption>, <th scope>, angka tabular kanan. --striped memberi zebra pada baris genap — warnanya sengaja setengah langkah surface, bukan langkah penuh yang dipakai hover, supaya hover tetap terbaca di baris yang sudah berwarna. Override lewat token --fdy-table-stripe.
| Faktur | Pelanggan | Status | Nilai |
|---|---|---|---|
| INV-1042 | PT Sumber Makmur | Lunas | 1.240.000 |
| INV-1043 | CV Berkah Jaya | Tertunda | 290.000 |
| INV-1044 | Toko Amanah | Draft | 0 |
Interaktif: cari global, urutkan (klik judul kolom), filter per kolom (ikon corong: teks berisi, enum checklist, angka rentang), pilih baris → bar aksi massal, paginasi. Butuh freeday-table.js.
| INV-1042 | PT Sumber Makmur | Lunas | 1.240.000 | |
| INV-1043 | CV Berkah Jaya | Tertunda | 290.000 | |
| INV-1044 | Toko Amanah | Draft | 0 | |
| INV-1045 | UD Maju Terus | Lunas | 4.750.000 | |
| INV-1046 | PT Cahaya Abadi | Tertunda | 820.000 | |
| INV-1047 | Koperasi Sejahtera | Batal | 150.000 | |
| INV-1048 | PT Sumber Makmur | Lunas | 3.120.000 | |
| INV-1049 | Toko Amanah | Draft | 67.000 | |
| INV-1050 | CV Berkah Jaya | Lunas | 1.990.000 | |
| INV-1051 | PT Cahaya Abadi | Tertunda | 540.000 |
Matriks yang dibaca terhadap dua sumbu sekaligus, kurs per mata uang (melintang) per tanggal (menurun), kehilangan maknanya begitu judulnya ter-scroll keluar. .fdy-table--sticky mengganti border-collapse jadi separate supaya garis dipegang oleh sel yang membeku: di bawah collapse, garis antar sel digambar oleh tabel, jadi kolom beku ter-scroll keluar dari garisnya sendiri.
Dua sumbu itu terpisah, dan sengaja: membekukan baris butuh scrollport vertikal, sedangkan tabel pemetaan yang ikut scroll halaman tidak punya, dan memaksakan keduanya jadi satu modifier membuat top:0 menempel di tepi viewport, menutupi app bar. Kolom pakai .fdy-table__freeze + --fdy-freeze-left per sel (offset harus datang dari pemanggil: itu jumlah lebar kolom ter-render, dan CSS tak bisa menjumlahkannya), header pakai .fdy-table--sticky-head di dalam .fdy-table-scroll--frozen yang tingginya diatur --fdy-table-frozen-h.
| Tanggal | USD | EUR | SGD | JPY | AUD | GBP | CNY | MYR |
|---|---|---|---|---|---|---|---|---|
| 01 Jul | 16.240 | 17.610 | 12.010 | 101 | 10.780 | 20.640 | 2.235 | 3.440 |
| 02 Jul | 16.255 | 17.588 | 12.024 | 101 | 10.802 | 20.611 | 2.238 | 3.446 |
| 03 Jul | 16.198 | 17.640 | 11.998 | 100 | 10.755 | 20.702 | 2.231 | 3.431 |
| 04 Jul | 16.301 | 17.522 | 12.077 | 102 | 10.844 | 20.560 | 2.244 | 3.459 |
| 05 Jul | 16.277 | 17.559 | 12.041 | 101 | 10.819 | 20.588 | 2.240 | 3.452 |
| 08 Jul | 16.150 | 17.701 | 11.962 | 100 | 10.711 | 20.780 | 2.226 | 3.421 |
| 09 Jul | 16.212 | 17.633 | 12.005 | 101 | 10.769 | 20.667 | 2.233 | 3.437 |
Scroll ke kanan dan ke bawah: kolom tanggal dan baris judul tetap, dengan garisnya.
Kolom beku memakai ground body, bukan ground header: tanpa itu, baris yang di-hover terputus di garis beku dan pelacakan baris melintasi grid lebar hilang, padahal itu justru alasan membekukannya.
Placeholder kosong / error untuk area data.
Sparkline, bar, line, area, dan donut, semuanya SVG/CSS murni tanpa dependency. Seri tunggal via data-values; multi-seri via data-series (JSON). Cartesian (line/area/bar) menggambar sumbu ber-tema (--chart-grid/--chart-tick), legenda otomatis untuk ≥2 seri, dan format nilai data-fdy-format="number|percent|currency". Warna dari token → chart ikut re-warna saat data-theme berubah. Multi-seri memakai palet kategorikal tervalidasi --chart-1…--chart-8 (urutan tetap); override lewat data-fdy-colors, memakai nama token semantik (primary…) atau slot chart-1…chart-8 untuk mengunci warna per kategori. Untuk data reaktif: panggil FreedayChart.update(el) atau pakai <FdyChart> dari @cahyo-dimas/freeday/vue. Butuh freeday-chart.js; tiap chart role="img" + aria-label + fallback tabel di dalam elemen.
6 bulan terakhir
Rp 18 jtLine multi-seri · sumbu persen
Area · sumbu USD
Bar bertumpuk (stacked)
Hierarki lewat <details> native, buka/tutup dengan klik atau Enter/Space, tanpa JS. Cabang bisa dilipat; daun = baris biasa. Varian checkbox (kanan) menambah pilihan bertingkat: centang cabang → semua anak ikut; sebagian anak → cabang jadi indeterminate. Butuh freeday-tree.js untuk cascade; centang tak pernah membuka/menutup cabang.
Dasar (tanpa JS)
Dengan checkbox (cascading)
__head menaruh tanda, judul, dan jalan keluar kartu dalam satu baris; kartu terakhir di sini juga menunjukkan .fdy-state--compact --danger untuk muatan yang gagal. --interactive = kursor + hover-lift untuk kartu yang diberi handler klik. Modifier ini presentasional: CSS tak bisa tahu handler-nya ada; pasangkan dengan kontrol nyata, biasanya .fdy-btn--stretch yang membentangkan area kliknya ke seluruh kartu. Kalau kartunya adalah kontrol (baris pemilih yang keyboard-reachable & diumumkan), render sebagai <button> dan tambah --button untuk me-reset kotak tombol UA tanpa membuang surface/border kartu.
Satu kartu, satu aksi utama, satu pintu keluar. Klik di mana saja → tombol utama; "Detail" tetap miliknya sendiri.
Jatuh tempo 30 Juli 2026. Total tagihan Rp 1.240.000.
Arahkan kursor dan kartu terangkat (hover-lift).
Inline untuk status; overlay untuk angka/dot yang menempel di sudut ikon, tombol, atau teks.
Varian semantik (--success, --warning, --danger, --info) menyatakan penilaian: lunas itu baik, batal itu buruk. Untuk kategori yang tak punya penilaian (jenis dokumen, departemen, kanal), pakai skala tone --tone-1…--tone-8: warnanya membedakan tanpa menyiratkan apa pun, dan tidak mencuri arti hijau/merah dari status yang benar-benar butuh.
Badge notifikasi = overlay. Bukan komponen terpisah dari “surat”: angka/“99+”/ikon/titik yang menempel di sudut elemen apa pun: lonceng, amplop, avatar, atau tombol. Bungkus konten dengan .fdy-badge-wrap, tambahkan .fdy-badge-ov. Warna ber-teks AA (default gelap · primary · danger); titik untuk accent/info/success/warning.
Ukuran --sm / default / --lg, plus grup bertumpuk. Untuk membedakan avatar berinisial sama (mis. banyak nama "PT …"), pakai --tone-1…--tone-8 adalah tint dekoratif yang tetap kontras AA di terang & gelap; hash indeks stabil dari nama lengkap (bukan dari inisial).
Tiga peran: default / hapus (tag statis atau filter aktif yang bisa dihapus), choice (toggle pilih), dan filter (toggle dengan centang). Yang interaktif = <button aria-pressed> dikelola freeday-chip.js: bungkus dalam data-fdy-chips, tambah data-single untuk pilih-satu.
Container rata untuk baris: satu permukaan ber-border, pembatas hairline, tanpa shadow. Ini yang dipakai saat .fdy-datatable berubah jadi daftar di bawah md, bukan tumpukan .fdy-card, yang memakai --shadow-lift sehingga sepuluh baris jadi sepuluh objek mengapung. Baris bisa jadi kontrol: render sebagai <button> + --button. Nol JS.
Beda dari .fdy-list-reset (utilitas yang cuma membuang bullet/indent bawaan browser).
Ubah urutan dengan menyeret pegangan, atau fokus ke pegangan lalu tekan ↑ / ↓. Garis menunjukkan tempat baris akan mendarat, dan tak ada yang berpindah sebelum tombol dilepas; Escape membatalkan. fdy-sort-change dapat dibatalkan: halaman statis membiarkannya dan kit memindah elemennya, framework membatalkannya lalu mengurutkan state-nya sendiri. Baris data-fdy-sort-fixed tak bisa dilewati. Butuh freeday-sortable.js.
Tampilan ringkas untuk halaman detail faktur / pelanggan. Nilai yang dibawa pembaca ke tempat lain (nomor dokumen, ID event) diberi tombol salin: data-fdy-copy="<nilai>" menyalin nilainya lalu memberi toast, termasuk saat clipboard tidak tersedia (http).
Menghalangi layar selama operasi yang tak boleh diinterupsi atau dikirim dua kali. Imperatif saja — Freeday.busy({ caption, delay, mark }) dan Freeday.idle() — karena API komponen mengundang dua instans, dan dua scrim dengan dua caption adalah persis kegagalan yang komponen ini cegah. Bukan dialog: tak ada yang ditanyakan dan tak ada yang bisa ditutup, interaksi dicabut dengan inert pada sisa halaman. Muncul setelah jeda 120 ms secara default, supaya operasi 80 ms tak sempat mengedipkan scrim.
--title/--text untuk teks, --circle untuk bentuk bulat, dan --avatar (+--avatar-sm/--avatar-lg) yang seukuran .fdy-avatar, jadi placeholder memesan kotak yang persis sama dan tak ada geser tata letak saat data datang.
Scroll-snap + panah + titik indikator. Keyboard ←/→, swipe di sentuh, autoplay opsional (data-fdy-autoplay, jeda saat hover/fokus). Butuh freeday-carousel.js.
Feed peristiwa vertikal: titik penanda pada garis penghubung, dengan waktu & deskripsi. Warna titik menandai status (didampingi teks, bukan warna saja). Pure CSS.
Faktur dibuat
INV-1042 untuk PT Sumber Makmur, Rp 1.240.000.
Dikirim ke pelanggan
Email terkirim ke finance@sumbermakmur.co.id.
Jatuh tempo mendekat
Sisa 5 hari sebelum jatuh tempo.
Lunas
Pembayaran diterima via transfer bank.
Bar atas standalone: brand · aksi · spacer. Bisa sticky, elevated, dense, atau berwarna. Tombol hamburger biasanya membuka drawer (menu samping).
Berwarna (primary) & dense, kontrol jadi on-color.
Product appbar: komposisi kaya di dalam .fdy-app__topbar (App shell), beda dari .fdy-appbar berdiri sendiri di atas. Urutan: nav toggle → .fdy-breadcrumb → judul halaman (.fdy-app__title, auto-spacer kanan) → pencarian (.fdy-input-group + __addon--icon) → menu pengguna (.fdy-menu). Salin blok ini langsung ke dalam <header class="fdy-app__topbar"> shell Anda.
Vue/React: useFreeday menghidrasi .fdy-menu otomatis lewat enhancer data-fdy-menu (mekanisme sama dengan freeday-menu.js vanilla), jadi cukup pasang markup di atas di dalam komponen, tak perlu wiring buka/tutup manual.
Panel samping temporary (overlay + backdrop) di atas <dialog> native, dengan focus-trap, Esc, dan kembalikan-fokus gratis. Kiri atau kanan. Slide masuk & keluar dengan backdrop yang mem-blur. Tutup lewat Esc/backdrop untuk melihat animasi keluarnya. Butuh freeday-drawer.js. (Tombol hamburger di App bar di atas juga membukanya.)
Native <details>, zero-JS dan aksesibel bawaan. Panel me-reveal (fade + rise) saat dibuka; chevron ikut berputar.
Bagian atas
Bagian bawah
Simpan cepat dengan Ctrl + S, tutup dialog dengan Esc.
Native <dialog>, dengan focus-trap, Esc, dan backdrop gratis dari browser. Muncul & menghilang dengan fade + skala halus (backdrop ikut mem-blur), simetris masuk-keluar. Untuk pertanyaan ya/tidak tidak perlu merakit dialog sendiri: await Freeday.confirm({ title, message, details, acceptText, tone }) menjawab true / false, dan Esc atau klik backdrop berarti tidak.
Semua komponen dirakit jadi layar faktur nyata.
Tiap string yang ditulis enhancer vanilla berbahasa Inggris sejak 2.0.0, sama dengan wrapper typed-nya. Sebelum itu default-nya Indonesia, dan aplikasi yang memakai dua jalur sekaligus jadi terbaca seperti dua produk. Sejak 1.39.0 setiap enhancer menyimpan teksnya dalam satu tabel TEXT yang dibaca lewat textOf(), jadi semuanya bisa diganti per elemen dengan data-fdy-text-<key>, tanpa fork dan tanpa merender ulang node-nya sendiri. Itu pula jalur migrasi buat aplikasi berbahasa Indonesia.
Placeholder {n}, {from}, {to}, {total}, {name}, {max} dan {label} disubstitusi bila string-nya mendukung. Pesan validasi memakai bentuk sama pada <form> (data-fdy-text-required, …), lebih longgar dari data-fdy-msg-<alias> per field yang tetap menang. Freeday.toast() menerima closeLabel lewat objek opsinya. Komponen bertipe Vue/React/Blazor sudah English dan tak butuh ini.
| INV-1042 | PT Sumber Makmur | 1,240,000 |
| INV-1043 | CV Bangun Jaya | 880,000 |
| INV-1044 | PT Andalan Niaga | 2,310,000 |
| INV-1045 | UD Terang Abadi | 415,000 |
| INV-1046 | PT Cahaya Timur | 1,690,000 |
Tabel yang sama dengan di atas; hanya atributnya yang berbeda. Buka corong filter pada kolom Customer: judul dan tombolnya ikut English.
Adapter Blazor menjangkau enhancer ini lewat FreedayBlazor.initAll, yang menjalankan semua enhancer terdaftar. Itulah kenapa jangkauan ini penting, dan kenapa default-nya tetap tidak berubah. npm test menjaga tak ada string enhancer yang ditulis di luar tabel TEXT-nya, jadi string baru datang dalam keadaan bisa di-override atau tidak sama sekali.
Utilitas tampilan responsif + provider JS. Skala (min-width): sm 600 · md 960 · lg 1280 · xl 1920. Kelas .fdy-hide-below-* / .fdy-hide-above-* menyembunyikan per rentang. freeday-breakpoint.js menstempel <html data-breakpoint> + memancarkan fdy-breakpoint-change. Ubah ukuran jendela untuk melihatnya.
Breakpoint saat ini: …
Kontrak yang dipegang setiap komponen, bukan renungan belakangan.
:focus-visible selalu terlihat, outline/ring 2–3px.aria-invalid + aria-describedby.prefers-reduced-motion.