Membangun design system bersama untuk microfrontend: package design-system dengan komponen dan design token, teknik isolasi CSS (prefix, CSS Modules, CSS-in-JS, Web Components), theming dengan CSS variables, versioning semver, dan dokumentasi dengan Storybook.

Setelah state dan auth terbagi di episode 7, sekarang saatnya merapikan tampilan. Di microfrontend, tanpa design system, tiap remote akan membawa gaya sendiri yang bentrok satu sama lain — tombol di catalog berbeda dengan tombol di checkout, warna tidak konsisten, dan tidak ada rasa "satu produk".
Mengapa penting? Karena design system adalah bahasa visual bersama yang membuat banyak remote terasa seperti satu aplikasi. Episode ini membahas cara membangun dan mengisolasi CSS di arsitektur yang terdiri dari banyak bundle independen.
design-systemKita taruh UI kit di packages/design-system sebagai package npm lokal dalam monorepo. Remote memakainya langsung seperti package biasa.
Komponen dasar: Button, Card, Input, Badge. Contoh Button dengan varian:
type Variant = 'primary' | 'ghost' | 'danger'
export function Button({
variant = 'primary',
children,
...rest
}: {
variant?: Variant
children: React.ReactNode
} & React.ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<button className={`btn btn--${variant}`} {...rest}>
{children}
</button>
)
}Token (warna, spacing, typography) didefinisikan sebagai CSS custom properties agar bisa dipakai semua remote dan memudahkan theming (detail di bawah):
:root {
--ds-color-primary: #2563eb;
--ds-color-bg: #ffffff;
--ds-color-text: #0f172a;
--ds-space-1: 0.25rem;
--ds-space-4: 1rem;
--ds-font-sans: 'Inter', system-ui, sans-serif;
}Masalah utama styling di microfrontend: class collision — dua remote memakai nama class yang sama (button) dengan gaya berbeda, dan yang terakhir dimuat menang. Ada beberapa strata isolasi:
Paling sederhana: beri prefix unik per package. Contoh btn--primary di design-system, dan remote memakai class catalog__card untuk layout remote.
CSS Modules me-namespace class secara otomatis per file, jadi tidak bentrok:
import styles from './Catalog.module.css'
export function Catalog() {
return <ul className={styles.list}>...</ul>
}CSS-in-JS menempel gaya ke komponen, biasanya aman dari collision. Namun pastikan pakai satu library yang sama (singleton) di semua remote agar tidak ada duplikasi runtime.
Untuk isolasi maksimal, bungkus remote dalam Web Component dengan Shadow DOM — gaya benar-benar terisolasi dari luar. Cocok bila kita benar-benar tidak percaya antar remote, tetapi ada biaya: gaya global (seperti token) tidak menembus shadow DOM tanpa disuntikkan.
Note
Untuk mayoritas kasus, prefix CSS + design token + satu library CSS-in-JS sudah cukup. Shadow DOM berlebihan kecuali kalian punya kebutuhan isolasi ketat atau remote dari vendor pihak ketiga.
Karena token kita sudah sebagai CSS variables, theming (dark/light) menjadi ringan lintas remote. Remote cukup mengonsumsi variabel — tidak perlu tahu tema apa yang aktif:
:root[data-theme='dark'] {
--ds-color-bg: #0f172a;
--ds-color-text: #f8fafc;
}Saat shell mengganti data-theme, seluruh remote yang memakai var(--ds-color-*) otomatis mengikuti. Ini satu contoh utama mengapa design token sebagai CSS variables adalah keputusan yang baik di microfrontend.
Design system harus mengikuti semver:
Remote boleh meng-upgrade design system pada jadwal masing-masing (independent). Remote yang masih di v1 dan yang sudah v2 bisa hidup berdampingan selama compatible (misal v1.5 dan v2.0 non-breaking). Kita bahas kompatibilitas versioning lebih dalam di episode 17.
Storybook dipakai untuk mendokumentasikan dan mem-preview design system secara terisolasi:
pnpm --filter design-system dlx storybook@latest initStorybook menampilkan setiap komponen dengan berbagai varian/state, menjadi kontrak visual antar tim frontend dan dasar untuk visual regression testing (episode 20).
Pada episode 8 ini, kalian telah memahami design system & styling bersama.
Inti yang harus dibawa pulang:
design-system adalah package lokal dengan komponen dan design token (CSS variables).Di episode 9 selanjutnya, kita akan membahas dynamic federation & runtime loading — memuat remote tanpa konfigurasi build-time via loadRemote, manifest-based, fallback loading dengan ErrorBoundary, dan progressive enhancement. Pastikan design system kalian tertata!