Table of Contents
▼- Kenapa Design System Biasa Tidak Cukup di Era AI Code Generation
- Tip #1: Gunakan Design Tokens dengan Naming Convention yang Semantik
- Tip #2: Buat Component API yang Eksplisit dan Predictable
- Tip #3: Dokumentasi Berbasis Contoh yang Executable
- Accessibility
- Design Tokens Used
- Template Starter Design System AI-Ready untuk Laravel + React
AI code generator seperti GitHub Copilot, v0 by Vercel, dan ChatGPT kini bisa generate komponen UI dalam hitungan detik.
Tapi ada masalah besar: jika design system kamu tidak terstruktur dengan baik, AI akan menghasilkan komponen yang inkonsisten, sulit di-maintain, dan bahkan melanggar guidelines yang sudah kamu tetapkan.
Bayangkan tim kamu menggunakan AI untuk generate tombol baru.
Hasilnya? Ada yang pakai bg-blue-500, ada yang bg-primary, ada yang malah hardcode #3B82F6.
Semua "biru", tapi tidak ada yang konsisten.
Inilah mengapa design system konvensional tidak cukup di era AI code generation.
Artikel ini akan membahas 5 tips konkret membangun design system yang AI-ready, khusus untuk tim startup Indonesia dengan resource terbatas.
Kenapa Design System Biasa Tidak Cukup di Era AI Code Generation
Design system tradisional dibuat untuk manusia: designer membuat mockup, developer melihat Figma, lalu menulis code.
Prosesnya linear dan ada human judgment di setiap tahap.
AI bekerja berbeda.
AI tidak "melihat" Figma seperti manusia.
AI membaca pattern dari code yang sudah ada, dokumentasi yang tertulis, dan naming convention yang kamu gunakan.
Jika design system kamu hanya berisi komponen visual tanpa struktur semantik yang jelas, AI akan:
- Generate komponen dengan styling yang berbeda-beda meskipun fungsinya sama
- Membuat variant baru yang tidak ada di design system
- Menggunakan value hardcoded alih-alih design tokens
- Mengabaikan accessibility guidelines karena tidak eksplisit di dokumentasi
Startup Indonesia sering menghadapi tantangan unik: tim kecil, deadline ketat, dan sering harus pivot cepat.
Design system yang AI-ready bukan hanya soal konsistensi visual, tapi juga velocity development.
Ketika AI bisa generate komponen yang langsung production-ready tanpa perlu banyak revisi, tim kamu bisa fokus ke problem solving yang lebih strategis.
Tip #1: Gunakan Design Tokens dengan Naming Convention yang Semantik
Design tokens adalah fondasi design system yang AI-readable.
Tapi bukan sembarang tokens.
AI butuh naming convention yang semantik, bukan hanya deskriptif.
Contoh naming yang buruk untuk AI:
--color-blue-500: #3B82F6;
--spacing-md: 16px;
--font-body: 'Inter', sans-serif;
Kenapa buruk?
Karena blue-500 tidak memberi konteks fungsi.
Apakah ini untuk primary button? Link? Background?
AI tidak tahu dan akan menggunakan sembarangan.
Contoh naming yang AI-friendly:
/* Semantic color tokens */
--color-primary: #3B82F6;
--color-primary-hover: #2563EB;
--color-primary-active: #1D4ED8;
--color-surface-default: #FFFFFF;
--color-surface-elevated: #F9FAFB;
--color-text-default: #111827;
--color-text-muted: #6B7280;
/* Semantic spacing tokens */
--spacing-component-gap: 16px;
--spacing-section-gap: 48px;
--spacing-inline: 8px;
/* Semantic typography tokens */
--font-body: 'Inter', sans-serif;
--font-heading: 'Plus Jakarta Sans', sans-serif;
--text-body-size: 16px;
--text-body-line-height: 1.5;
--text-heading-1-size: 32px;
--text-heading-1-weight: 700;
Perhatikan perbedaannya: setiap token memiliki konteks penggunaan yang jelas.
Ketika AI membaca --color-primary, ia tahu ini untuk elemen utama aplikasi.
Ketika AI membaca --spacing-component-gap, ia tahu ini untuk jarak antar komponen.
Implementasi praktis untuk Laravel + React:
Buat file tokens.css di root project:
:root {
/* Brand colors */
--brand-primary: #3B82F6;
--brand-secondary: #10B981;
/* Semantic colors */
--color-primary: var(--brand-primary);
--color-primary-hover: #2563EB;
--color-success: var(--brand-secondary);
--color-danger: #EF4444;
--color-warning: #F59E0B;
/* Surface colors */
--surface-default: #FFFFFF;
--surface-elevated: #F9FAFB;
--surface-overlay: rgba(0, 0, 0, 0.5);
/* Text colors */
--text-default: #111827;
--text-muted: #6B7280;
--text-inverse: #FFFFFF;
/* Spacing scale */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 16px;
--space-lg: 24px;
--space-xl: 32px;
--space-2xl: 48px;
/* Component spacing */
--gap-component: var(--space-md);
--gap-section: var(--space-2xl);
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--text-xs: 12px;
--text-sm: 14px;
--text-base: 16px;
--text-lg: 18px;
--text-xl: 20px;
--text-2xl: 24px;
--text-3xl: 32px;
/* Border radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
Export tokens ke JavaScript untuk React components:
// tokens.js
export const tokens = {
colors: {
primary: 'var(--color-primary)',
primaryHover: 'var(--color-primary-hover)',
success: 'var(--color-success)',
danger: 'var(--color-danger)',
textDefault: 'var(--text-default)',
textMuted: 'var(--text-muted)',
},
spacing: {
xs: 'var(--space-xs)',
sm: 'var(--space-sm)',
md: 'var(--space-md)',
lg: 'var(--space-lg)',
xl: 'var(--space-xl)',
},
typography: {
fontSans: 'var(--font-sans)',
textBase: 'var(--text-base)',
textLg: 'var(--text-lg)',
},
radius: {
sm: 'var(--radius-sm)',
md: 'var(--radius-md)',
lg: 'var(--radius-lg)',
},
};
Dengan struktur ini, ketika AI generate komponen baru, ia akan menggunakan tokens yang sudah terdefinisi, bukan hardcode values.
Tip #2: Buat Component API yang Eksplisit dan Predictable
AI belajar dari pattern.
Jika component API kamu tidak konsisten, AI akan generate props yang aneh atau bahkan tidak valid.
Contoh Button component yang buruk untuk AI:
// ❌ Inconsistent API
<Button color="blue" size="md" />
<Button variant="primary" buttonSize="large" />
<Button type="filled" styling="small" />
Setiap developer (atau AI) bisa menggunakan props yang berbeda untuk hasil yang sama.
Ini chaos.
Contoh Button component yang AI-friendly:
// ✅ Explicit, consistent API
<Button variant="primary" size="md" />
<Button variant="secondary" size="lg" />
<Button variant="danger" size="sm" />
Prop names harus:
- Konsisten di semua komponen
- Predictable: jika ada
variantdi Button, komponen lain juga pakaivariant - Terbatas: gunakan union types atau enum, bukan free text
Implementasi di React + TypeScript:
// Button.tsx
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost';
type ButtonSize = 'sm' | 'md' | 'lg';
interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
fullWidth?: boolean;
disabled?: boolean;
loading?: boolean;
children: React.ReactNode;
onClick?: () => void;
}
export const Button: React.FC<ButtonProps> = ({
variant = 'primary',
size = 'md',
fullWidth = false,
disabled = false,
loading = false,
children,
onClick,
}) => {
const baseClasses = 'inline-flex items-center justify-center font-medium rounded-md transition-colors';
const variantClasses = {
primary: 'bg-[var(--color-primary)] text-white hover:bg-[var(--color-primary-hover)]',
secondary: 'bg-gray-200 text-gray-900 hover:bg-gray-300',
danger: 'bg-[var(--color-danger)] text-white hover:bg-red-700',
ghost: 'bg-transparent hover:bg-gray-100',
};
const sizeClasses = {
sm: 'text-sm px-3 py-1.5',
md: 'text-base px-4 py-2',
lg: 'text-lg px-6 py-3',
};
const className = [
baseClasses,
variantClasses[variant],
sizeClasses[size],
fullWidth && 'w-full',
disabled && 'opacity-50 cursor-not-allowed',
].filter(Boolean).join(' ');
return (
<button
className={className}
disabled={disabled || loading}
onClick={onClick}
>
{loading && <span className="mr-2">⏳</span>}
{children}
</button>
);
};
Kenapa ini AI-friendly?
- Props terbatas dengan TypeScript union types
- Naming convention konsisten (
variant,size) - Default values eksplisit
- Classes menggunakan design tokens
Ketika AI melihat pattern ini, ia akan generate komponen lain dengan struktur yang sama.
Misalnya, jika kamu punya Card component, AI akan otomatis menggunakan variant dan size juga.
Tip #3: Dokumentasi Berbasis Contoh yang Executable
Dokumentasi design system biasanya berisi screenshot dan deskripsi.
Itu cukup untuk manusia, tapi tidak untuk AI.
AI butuh executable examples: code yang bisa langsung dijalankan dan menunjukkan semua variant.
Butuh jasa pembuatan website profesional? KerjaKode menyediakan layanan pembuatan website berkualitas tinggi dengan harga terjangkau. Kunjungi jasa pembuatan website KerjaKode untuk konsultasi gratis dan wujudkan website impian Anda.
Format dokumentasi yang AI-friendly:
Buat file Button.stories.tsx (menggunakan Storybook atau MDX):
// Button.stories.tsx
import { Button } from './Button';
export default {
title: 'Components/Button',
component: Button,
};
// Story: All variants
export const Variants = () => (
<div className="space-y-4">
<Button variant="primary">Primary Button</Button>
<Button variant="secondary">Secondary Button</Button>
<Button variant="danger">Danger Button</Button>
<Button variant="ghost">Ghost Button</Button>
</div>
);
// Story: All sizes
export const Sizes = () => (
<div className="space-y-4">
<Button size="sm">Small Button</Button>
<Button size="md">Medium Button</Button>
<Button size="lg">Large Button</Button>
</div>
);
// Story: States
export const States = () => (
<div className="space-y-4">
<Button disabled>Disabled Button</Button>
<Button loading>Loading Button</Button>
</div>
);
// Story: Full width
export const FullWidth = () => (
<Button fullWidth variant="primary">
Full Width Button
</Button>
);
// Story: Common use cases
export const UseCases = () => (
<div className="space-y-4">
<div>
<h3>Form submit</h3>
<Button variant="primary" size="lg">
Submit Form
</Button>
</div>
<div>
<h3>Cancel action</h3>
<Button variant="secondary">Cancel</Button>
</div>
<div>
<h3>Delete action</h3>
<Button variant="danger">Delete Item</Button>
</div>
</div>
);
Dokumentasi seperti ini memberikan AI context lengkap:
- Semua variant yang valid
- Semua kombinasi props yang umum
- Use cases nyata di aplikasi
Ketika AI diminta generate form dengan submit button, ia bisa langsung reference story UseCases dan tahu harus pakai variant="primary" dan size="lg".
Tambahkan README.md yang structured:
# Button Component
## Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| variant | 'primary' \| 'secondary' \| 'danger' \| 'ghost' | 'primary' | Visual style variant |
| size | 'sm' \| 'md' \| 'lg' | 'md' | Button size |
| fullWidth | boolean | false | Make button full width |
| disabled | boolean | false | Disable button |
| loading | boolean | false | Show loading state |
## Usage
```jsx
import { Button } from '@/components/Button';
// Primary button
<Button variant="primary" onClick={handleSubmit}>
Submit
</Button>
// Secondary button
<Button variant="secondary" onClick={handleCancel}>
Cancel
</Button>
// Danger button for destructive actions
<Button variant="danger" onClick={handleDelete}>
Delete
</Button>
Accessibility
- Uses semantic
<button>element - Supports keyboard navigation
- Shows loading state with visual indicator
- Disabled state prevents interaction
Design Tokens Used
--color-primary: Primary variant background--color-danger: Danger variant background--space-md: Default padding
Format tabel dan contoh code yang structured memudahkan AI mem-parse informasi.
## Tip #4: Buat Variant yang Eksplisit, Bukan Kombinasi Props
Developer sering membuat komponen dengan banyak props yang bisa dikombinasikan bebas.
Ini flexible tapi sangat buruk untuk AI.
**Contoh yang buruk:**
```jsx
// ❌ Too many possible combinations
<Button
color="blue"
outlined
rounded
elevated
large
/>
Berapa kombinasi valid yang mungkin? Ratusan.
AI akan bingung dan generate kombinasi yang tidak masuk akal.
Contoh yang AI-friendly:
// ✅ Limited, explicit variants
<Button variant="primary-outlined" size="lg" />
<Button variant="secondary-rounded" size="md" />
Atau lebih baik lagi, pisahkan concerns:
// ✅ Separate visual variant from functional props
<Button
variant="primary" // visual style
size="lg" // size
fullWidth // layout
/>
Implementasi Card component dengan variant eksplisit:
// Card.tsx
type CardVariant = 'default' | 'elevated' | 'outlined' | 'interactive';
interface CardProps {
variant?: CardVariant;
padding?: 'none' | 'sm' | 'md' | 'lg';
children: React.ReactNode;
onClick?: () => void;
}
export const Card: React.FC<CardProps> = ({
variant = 'default',
padding = 'md',
children,
onClick,
}) => {
const baseClasses = 'rounded-lg bg-white';
const variantClasses = {
default: '',
elevated: 'shadow-lg',
outlined: 'border border-gray-200',
interactive: 'hover:shadow-md transition-shadow cursor-pointer',
};
const paddingClasses = {
none: '',
sm: 'p-4',
md: 'p-6',
lg: 'p-8',
};
const className = [
baseClasses,
variantClasses[variant],
paddingClasses[padding],
].join(' ');
return (
<div className={className} onClick={onClick}>
{children}
</div>
);
};
Setiap variant punya purpose yang jelas:
default: Card biasaelevated: Card dengan shadow untuk emphasisoutlined: Card dengan border untuk subtle separationinteractive: Card yang bisa diklik
AI bisa dengan mudah memilih variant yang tepat berdasarkan context.
Dokumentasikan kapan menggunakan setiap variant:
## When to Use Each Variant
* **default**: Standard card for grouping content
* **elevated**: Use when card needs to stand out from background
* **outlined**: Use for subtle separation in light backgrounds
* **interactive**: Use when card is clickable (e.g., navigation cards)
## Examples
```jsx
// Product listing
<Card variant="interactive" onClick={handleClick}>
<ProductPreview />
</Card>
// Dashboard stats
<Card variant="elevated">
<StatWidget />
</Card>
// Form section
<Card variant="outlined" padding="lg">
<FormFields />
</Card>
Dengan dokumentasi yang explicit seperti ini, AI tahu kapan menggunakan variant tertentu.
## Tip #5: Uji Design System Kamu dengan AI Code Generator
Cara terbaik memastikan design system kamu AI-ready adalah dengan **mengujinya langsung** menggunakan AI code generator.
Jangan tunggu sampai production.
Test di fase development.
**Metode pengujian:**
**1. Prompt consistency test:**
Minta AI generate komponen yang sama dengan prompt berbeda.
Prompt 1: "Create a primary button for form submission" Prompt 2: "Make a submit button with primary styling" Prompt 3: "I need a button for submitting forms, use primary color"
Jika design system kamu baik, ketiga prompt akan generate button dengan props yang sama: `variant="primary"`.
**2. Component combination test:**
Minta AI generate komponen kompleks yang menggunakan multiple components dari design system.
Prompt: "Create a login form with email input, password input, and submit button"
AI harus generate form yang:
* Menggunakan design tokens untuk spacing
* Menggunakan variant yang konsisten
* Mengikuti accessibility guidelines
**3. Variant coverage test:**
Minta AI generate semua variant dari satu komponen.
Prompt: "Show me all button variants available in the design system"
AI harus bisa list `primary`, `secondary`, `danger`, `ghost` — semua variant yang kamu dokumentasikan.
**4. Edge case test:**
Minta AI handle edge cases.
Prompt: "Create a loading button that's disabled and full width"
AI harus generate:
```jsx
<Button
variant="primary"
loading
disabled
fullWidth
>
Processing...
</Button>
Gunakan AI tools populer untuk testing:
- GitHub Copilot: Test inline code completion
- v0 by Vercel: Test full component generation
- ChatGPT/Claude: Test dengan prompt natural language
Contoh testing workflow dengan GitHub Copilot:
// Start typing...
// "Create a card with product image, title, price, and add to cart button"
// Good AI output (if design system is AI-ready):
<Card variant="interactive">
<img src={product.image} alt={product.name} />
<h3 className="text-lg font-semibold">{product.name}</h3>
<p className="text-muted">{formatPrice(product.price)}</p>
<Button variant="primary" fullWidth onClick={handleAddToCart}>
Add to Cart
</Button>
</Card>
// Bad AI output (if design system is not AI-ready):
<div className="card shadow-md rounded-lg p-4">
<img src={product.image} />
<h3 style={{ fontSize: '18px', fontWeight: 'bold' }}>{product.name}</h3>
<p style={{ color: '#666' }}>{product.price}</p>
<button className="btn-primary w-full" onClick={handleAddToCart}>
Add to Cart
</button>
</div>
Perhatikan perbedaan output: yang pertama menggunakan components dan tokens dari design system, yang kedua hardcode styling.
Iterasi berdasarkan hasil testing:
Jika AI generate code yang tidak sesuai design system, ada beberapa kemungkinan:
- Dokumentasi kurang jelas → tambahkan more examples
- Naming convention tidak intuitif → refactor tokens
- Variant terlalu banyak → simplify ke yang essential
- Props API tidak konsisten → standardize across components
Template Starter Design System AI-Ready untuk Laravel + React
Berikut structure folder dan starter code yang bisa langsung kamu pakai.
Project structure:
project/
├── resources/
│ ├── css/
│ │ ├── tokens.css # Design tokens
│ │ └── app.css # Main styles
│ ├── js/
│ │ ├── components/
│ │ │ ├── Button/
│ │ │ │ ├── Button.tsx
│ │ │ │ ├── Button.stories.tsx
│ │ │ │ └── README.md
│ │ │ ├── Card/
│ │ │ │ ├── Card.tsx
│ │ │ │ ├── Card.stories.tsx
│ │ │ │ └── README.md
│ │ │ └── Input/
│ │ │ ├── Input.tsx
│ │ │ ├── Input.stories.tsx
│ │ │ └── README.md
│ │ ├── tokens.ts # JS tokens export
│ │ └── app.tsx
│ └── views/
├── docs/
│ ├── design-system.md # Main documentation
│ └── ai-guidelines.md # Guidelines for AI
└── .storybook/ # Storybook config
Starter tokens.css:
:root {
/* Brand */
--brand-primary: #3B82F6;
--brand-secondary: #10B981;
/* Semantic colors */
--color-primary: var(--brand-primary);
--color-primary-hover: #2563EB;
--color-primary-active: #1D4ED8;
--color-success: var(--brand-secondary);
--color-danger: #EF4444;
--color-warning: #F59E0B;
--color-info: #3B82F6;
/* Neutral colors */
--color-surface-default: #FFFFFF;
--color-surface-elevated: #F9FAFB;
--color-surface-overlay: rgba(0, 0, 0, 0.5);
--color-border: #E5E7EB;
/* Text colors */
--color-text-default: #111827;
--color-text-muted: #6B7280;
--color-text-inverse: #FFFFFF;
--color-text-link: var(--color-primary);
/* Spacing scale (8px base) */
--space-0: 0;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
--space-16: 64px;
/* Typography */
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', 'Courier New', monospace;
--text-xs: 12px;
--text-sm: 14px;
--text-base: 16px;
--text-lg: 18px;
--text-xl: 20px;
--text-2xl: 24px;
--text-3xl: 30px;
--text-4xl: 36px;
--font-normal: 400;
--font-medium: 500;
--font-semibold: 600;
--font-bold: 700;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
--line-height-relaxed: 1.75;
/* Border radius */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-xl: 16px;
--radius-full: 9999px;
/* Shadows */
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
--shadow-sm: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
/* Animation */
--transition-fast: 150ms ease-in-out;
--transition-base: 200ms ease-in-out;
--transition-slow: 300ms ease-in-out;
/* Z-index scale */
--z-base: 1;
--z-dropdown: 100;
--z-sticky: 200;
--z-modal: 300;
--z-popover: 400;
--z-tooltip: 500;
}
Starter Input.tsx component:
// Input.tsx
import React from 'react';
type InputVariant = 'default' | 'error' | 'success';
type InputSize = 'sm' | 'md' | 'lg';
interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
variant?: InputVariant;
size?: InputSize;
label?: string;
error?: string;
helperText?: string;
fullWidth?: boolean;
}
export const Input: React.FC<InputProps> = ({
variant = 'default',
size = 'md',
label,
error,
helperText,
fullWidth = false,
className = '',
...props
}) => {
const baseClasses = 'border rounded-md transition-colors focus:outline-none focus:ring-2';
const variantClasses = {
default: 'border-gray-300 focus:border-[var(--color-primary)] focus:ring-[var(--color-primary)]/20',
error: 'border-red-500 focus:border-red-500 focus:ring-red-500/20',
success: 'border-green-500 focus:border-green-500 focus:ring-green-500/20',
};
const sizeClasses = {
sm: 'text-sm px-3 py-1.5',
md: 'text-base px-4 py-2',
lg: 'text-lg px-5 py-3',
};
const inputClassName = [
baseClasses,
variantClasses[error ? 'error' : variant],
sizeClasses[size],
fullWidth && 'w-full',
className,
].filter(Boolean).join(' ');
return (
<div className={fullWidth ? 'w-full' : ''}>
{label && (
<label className="block text-sm font-medium text-gray-700 mb-1">
{label}
</label>
)}
<input className={inputClassName} {...props} />
{error && (
<p className="mt-1 text-sm text-red-600">{error}</p>
)}
{helperText && !error && (
<p className="mt-1 text-sm text-gray-500">{helperText}</p>
)}
</div>
);
};
Starter ai-guidelines.md:
# AI Code Generation Guidelines
This document provides guidelines for AI code generators working with this design system.
## Component Usage Rules
### Button
Use `Button` component for all clickable actions:
```jsx
// Form submit
<Button variant="primary" size="lg">Submit</Button>
// Cancel action
<Button variant="secondary">Cancel</Button>
// Destructive action
<Button variant="danger">Delete</Button>
// Low-priority action
<Button variant="ghost">Skip</Button>
Input
Use Input component for all text inputs:
// Basic input
<Input label="Email" type="email" />
// Input with error
<Input label="Password" type="password"