Mengapa token dulu
Sebelum halaman pertama dibuat, saya menulis token desain. Token adalah nama untuk setiap keputusan visual: warna teks, warna permukaan, jari-jari sudut kartu, bayangan, ukuran huruf, dan jarak. Nama itu dipakai oleh semua komponen, sehingga keputusan visual hanya ditulis sekali. Kalau suatu hari warna aksen berubah, saya mengubah satu baris, bukan mencari puluhan nilai heksadesimal yang tersebar di banyak berkas.
Ada alasan kedua yang lebih penting untuk proyek ini. Situs publik dan platform belajar adalah dua aplikasi yang berbeda. Situs publik dibangun dengan Astro dan nyaris tanpa JavaScript, sedangkan platform belajar dibangun dengan Next.js dan React. Tanpa sumber bersama, keduanya pelan-pelan akan terlihat seperti dua produk yang tidak saling kenal. Dengan token di satu paket, keduanya membaca nilai yang sama, dan mahasiswa yang pindah dari situs ke portal kuliah tidak merasa masuk ke tempat lain.
Alasan ketiga adalah pengecekan otomatis. Karena semua warna punya nama, CI bisa menolak setiap nilai heksadesimal yang muncul di luar paket token. Aturan yang biasanya hanya hidup di kepala perancang sekarang dijaga oleh mesin, dan saya tidak perlu mengandalkan ingatan saat meninjau kode pada larut malam.
Bentuk token
Token ditulis sebagai objek TypeScript biasa. Setiap kelompok punya peran: warna, jari-jari,
bayangan, skala huruf, jarak, tata letak, dan gerak. Nama warna mengikuti peran, bukan nilainya.
Saya menulis text dan accent, bukan merah-tua dan merah. Dengan begitu tema gelap kelak cukup
memberi nilai baru untuk peran yang sama, tanpa mengubah satu komponen pun.
export const color = {
text: "#6B0A0C",
surface: "#FFFFFF",
accent: "#B62029",
muted: "#7A3B36",
} as const;Objek ini punya dua pembaca. Yang pertama adalah skrip kecil yang menulis berkas CSS berisi blok
@theme untuk Tailwind dan variabel biasa di :root. Yang kedua adalah kode yang membutuhkan nilai
mentah, misalnya pembuat gambar pratinjau media sosial. Gambar itu dibuat di server saat build, jadi
ia tidak bisa membaca variabel CSS. Ia mengimpor objek yang sama, sehingga warna di gambar pratinjau
tidak mungkin berbeda dari warna di halaman.
Saya sengaja tidak memakai alat khusus untuk token. Alat seperti itu berguna kalau ada ratusan nilai dan banyak platform tujuan. Di sini hanya ada sekitar empat puluh nilai dan dua tujuan, jadi skrip dua puluh baris sudah cukup dan tidak menambah dependensi yang harus dirawat.
Dari token ke CSS
Skrip pembangun berjalan sebelum aplikasi apa pun dibangun. Turborepo mengatur urutannya: tugas
dev dan build di setiap aplikasi bergantung pada build di paket yang dipakainya. Hasilnya satu
berkas tokens.css yang diimpor tepat setelah Tailwind.
@import "tailwindcss";
@import "@awesome/tokens/tokens.css";Blok @theme membuat Tailwind mengenal nama-nama itu sebagai utilitas. Kelas bg-tint-amber atau
rounded-lg langsung tersedia dan selalu sesuai dengan token. Saya juga menghapus palet bawaan
Tailwind, sehingga kelas seperti bg-blue-500 tidak ada sama sekali. Tidak ada jalan pintas untuk
memakai warna di luar token, bahkan secara tidak sengaja ketika menyalin contoh dari dokumentasi.
Variabel di :root dipakai oleh gaya terlingkup di komponen Astro. Beberapa komponen lebih mudah
dibaca dengan CSS biasa daripada deretan kelas yang panjang, terutama kalau ada keadaan seperti
aria-pressed atau aria-current. Keduanya membaca nilai yang sama, jadi pilihan antara kelas
utilitas dan CSS biasa adalah soal keterbacaan, bukan soal konsistensi.
Tipografi yang lentur
Judul di situs ini besar dan lebar. Ukurannya memakai clamp(), sehingga judul tumbuh mulus dari
layar ponsel 360 px sampai layar lebar tanpa titik henti. Setiap ukuran judul di maket punya token
sendiri, dari judul utama beranda sampai judul kartu kecil.
Huruf yang dipakai hanya dua: Archivo untuk teks dan JetBrains Mono untuk kode. Keduanya berkas variabel WOFF2 yang disimpan di repositori, hanya dengan subset Latin. Bahasa Indonesia tidak membutuhkan karakter lain, jadi berkasnya kecil. Archivo punya sumbu lebar, sehingga judul bisa dibuat sangat lebar tanpa memuat keluarga huruf ketiga.
Hanya Archivo tegak yang dimuat lebih dulu. Huruf miring dan huruf kode baru diunduh ketika halaman benar-benar memakainya. Astro membuat huruf cadangan dengan metrik yang disesuaikan, sehingga teks tidak melompat ketika huruf asli selesai dimuat. Ini penting untuk skor pergeseran tata letak, yang anggarannya hanya 0,05.
Tata letak bersama
Setiap halaman memakai satu tata letak dasar. Tata letak itu menulis judul halaman, deskripsi, URL kanonis, tag Open Graph, data terstruktur untuk pemilik situs, dan skrip analitik kalau alamatnya diatur. Di badan halaman ada tautan lompat ke konten utama, kepala situs, isi halaman, ajakan penutup, dan kaki halaman.
Kepala situs punya dua bentuk. Di layar lebar, semua tautan tampil berjajar dengan tombol portal
mahasiswa di ujung kanan. Di bawah 960 px, kepala situs hanya berisi logo dan tombol menu. Menu
terbuka sebagai elemen dialog bawaan peramban, yang sudah menangani tombol Escape dan menjaga fokus
tetap di dalam menu. Skrip yang saya tulis hanya membuka dan menutupnya, ditambah menutup menu saat
pengguna memilih salah satu tautan.
Halaman yang sedang dibuka ditandai dengan huruf tebal dan garis bawah oranye untuk mata, dan dengan
aria-current="page" untuk teknologi bantu. Dua penanda itu selalu datang bersamaan karena
berasal dari satu sumber data, sehingga tidak mungkin yang satu muncul tanpa yang lain.
Ajakan penutup berbeda di setiap halaman. Di beranda bunyinya “Punya proyek? Mari bicara.”, di halaman artikel “Ada pertanyaan tentang artikel ini?”. Teksnya disimpan di berkas profil, dikunci dengan nama halaman. Halaman kontak tidak punya ajakan penutup karena halaman itu sendiri adalah ajakannya.
Gerak yang tenang
Ada sedikit gerak di situs ini: pita peran yang bergeser pelan dan perubahan warna saat tombol disorot. Semua gerak ditulis dengan CSS, tanpa pustaka animasi. Satu aturan global mematikan semua animasi dan transisi untuk pengunjung yang memilih mengurangi gerak di sistem operasinya.
Aturan itu diuji. Pengujian Playwright membuka halaman dengan preferensi gerak dikurangi, lalu memeriksa bahwa durasi animasi pada elemen yang bergerak praktis nol. Kalau suatu hari saya menambahkan animasi baru dan lupa, pengujian itu yang akan mengingatkan sebelum perubahan sampai ke produksi.
Halaman katalog
Semua komponen bersama tampil di satu halaman tersembunyi, /styleguide. Setiap komponen tampil
dalam setiap keadaannya: biasa, fokus, ditekan, nonaktif, kosong, memuat, galat, dan berhasil.
Halaman itu tidak ditautkan dari mana pun, tidak masuk peta situs, dan diberi tanda noindex.
Manfaatnya terasa ketika meninjau perubahan. Daripada membuka lima halaman untuk melihat apakah tombol masih benar, saya membuka satu halaman. Pengujian aksesibilitas juga berjalan di halaman itu, jadi setiap keadaan komponen diperiksa oleh axe, termasuk keadaan yang jarang muncul di halaman sungguhan seperti galat dan kosong.
Anggaran sejak hari pertama
Pipa CI dipasang bersamaan dengan token, bukan setelah situs jadi. Setiap perubahan diperiksa oleh Biome, TypeScript, Vitest, Playwright dengan axe di tiga lebar layar, dan Lighthouse dengan anggaran ponsel. Anggaran itu ketat: skor kinerja minimal 95, LCP paling lama dua detik, dan JavaScript paling banyak 50 KB per halaman.
Memasang anggaran saat situs masih kosong terasa berlebihan, tetapi justru di situ nilainya. Anggaran yang dipasang belakangan selalu menemukan banyak pelanggaran sekaligus, dan godaannya adalah melonggarkan angka. Anggaran yang dipasang di awal menolak pelanggaran pertama, ketika masih mudah diperbaiki dan penyebabnya masih jelas.
Yang belum dikerjakan
Tema gelap belum ada. Token sudah diberi nama berdasarkan peran, jadi menambahkannya kelak hanya mengubah paket token. Tetapi palet gelap belum dirancang, dan saya tidak mau mengirim tampilan yang belum pernah dilihat oleh siapa pun. Menebak warna gelap dari warna terang biasanya menghasilkan kontras yang buruk atau suasana yang berbeda dari maksud aslinya. Lebih jujur menunda fitur itu dan menuliskannya sebagai keputusan, daripada mengirimnya setengah jadi lalu memperbaikinya diam-diam.
Komponen untuk platform belajar juga belum memakai token ini. Itu akan dikerjakan bersama fitur pertama di platform belajar, karena baru di sana kebutuhan komponennya jelas dan bisa diuji dengan data sungguhan dari mahasiswa.
Berikutnya
Bagian ketiga seri ini membahas sinkronisasi dari Notion ke Git: bagaimana tulisan di Notion menjadi berkas MDX di repositori setiap 30 menit, dan bagaimana sinkronisasi itu dibuat aman sehingga respons kosong atau galat dari API tidak pernah menghapus tulisan yang sudah terbit.

