How Long Does It Take to Build a Design System Button? Before/After Comparison
Source: Dev.to
“Where’s the design system documentation?”
If you’ve worked on government or enterprise projects, you’ve heard this. Then you open a 200‑page PDF, hunt for color codes, copy spacing values, debate whether the border‑radius is 4 px or 8 px… half a day gone for one button.
Today I’m showing you actual code — before and after using a component library.
Case 1: Building a Button
Before: Implementing from Design System PDF
// Step 1: Find color values in design guide (10 min)
// Primary: #0A5ECA, Hover: #0852B2, Active: #064794...
// Step 2: Write CSS (30 min)
.btn-primary {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
min-width: 80px;
height: 48px;
padding: 0 20px;
border-radius: 6px;
font-size: 16px;
font-weight: 500;
background-color: #0A5ECA;
color: #FFFFFF;
border: none;
cursor: pointer;
transition: background-color 0.2s;
}
.btn-primary:hover { background-color: #0852B2; }
.btn-primary:active { background-color: #064794; }
.btn-primary:disabled{
opacity: 0.5;
cursor: not-allowed;
}
.btn-primary:focus-visible{
outline: 2px solid #0A5ECA;
outline-offset: 2px;
}
// Step 3: Write component (20 min)
function Button({ children, ...props }) {
return (
<button {...props}>{children}</button>
);
}
// Step 4: Add variants? (1 hour)
// secondary, tertiary, danger… find each color, add CSS
// Step 5: Add sizes? (30 min)
// xs, sm, md, lg, xl… calculate height, padding, font‑size
// Total time: 2‑3 hours
// And next project? Start over.
After: Using HANUI
npx hanui add button
import { Button } from '@/components/ui/button';
// Done. Use immediately.
<Button>Default Button</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="danger">Delete</Button>
<Button isLoading>Saving…</Button>
<Button icon={<SearchIcon />}>Search</Button>
Time: 10 seconds
8 variants, 6 sizes, loading state, icon support — all included. Accessibility (aria-busy, aria-disabled, focus ring) is applied automatically.
Case 2: Form Field
Before: Manual Implementation
function FormField({ label, error, helperText, required, children }) {
const id = useId();
const errorId = `${id}-error`;
const helperId = `${id}-helper`;
return (
<div>
<label htmlFor={id}>
{label}
{required && <span>*</span>}
</label>
{/* How do I pass id, aria-describedby to children? */}
{/* cloneElement? Context? */}
{children}
{error && (
<p id={errorId}>{error}</p>
)}
{helperText && (
<p id={helperId}>{helperText}</p>
)}
</div>
);
}
// aria-describedby connection?
// Input error styling?
// Error icon?
// Screen reader support?
// … complexity grows
After: Using HANUI
npx hanui add form-field input
import {
FormField,
FormLabel,
FormError,
FormHelperText,
} from '@/components/ui/form-field';
import { Input } from '@/components/ui/input';
<FormField>
<FormLabel>Email</FormLabel>
<Input type="email" />
<FormError>Invalid email format</FormError>
</FormField>
<FormField>
<FormLabel>Username</FormLabel>
<Input type="text" />
<FormHelperText>Username is available</FormHelperText>
</FormField>
Context automatically connects id and ARIA attributes. Error/success icons auto‑display. role="alert" and aria-live="polite" are applied.
Case 3: Header + Mega Menu
Building this from scratch takes days.
Before: Consider All These
- Responsive (mobile hamburger menu)
- Mega‑menu dropdown
- Keyboard navigation (Tab, Arrow, Escape)
- Focus trap
- Scroll behavior (sticky/hide)
- WAI‑ARIA patterns
Honestly, building this properly takes a week.
After: Using HANUI
npx hanui add header
import { HeaderWithMegaMenu } from '@/components/ui/header';
// Use <HeaderWithMegaMenu /> directly – mega menu, mobile support,
// keyboard navigation, scroll behavior are all included.
Real Comparison
| Task | Manual | With Library |
|---|---|---|
| Button (with variants) | 2‑3 hours | 10 seconds |
| Form Field (accessible) | 3‑4 hours | 10 seconds |
| Header + Mega Menu | 3‑5 days | 10 seconds |
| Select (search, multi) | 1‑2 days | 10 seconds |
| Modal (focus trap) | Half day | 10 seconds |
Things Often Missed When Building Manually
aria-describedbyconnectionaria-expanded,aria-haspopup- Focus management
- Escape key to close
- Screen‑reader announcements
The Hidden Cost
The real problem with manual implementation isn’t just time.
1. Inconsistency
/* Developer A */
.btn { border-radius: 4px; }
/* Developer B */
.button { border-radius: 6px; }
/* Developer C */
.cus { border-radius: 5px; }
When each team member copies‑pastes values from a PDF, subtle differences creep in, leading to a fragmented UI and extra maintenance overhead.
Using a component library like HANUI eliminates the guesswork, guarantees accessibility, and lets you ship features in seconds instead of days.
tom-btn {
border-radius: 8px;
}
2. Missing Accessibility
When you’re busy, ARIA attributes get skipped. “I’ll add them later” becomes “failed the audit.”
3. Maintenance Hell
Design system updates? Manually update all components across all projects.
Summary
| Approach | Time required |
|---|---|
| Building design‑system components from scratch | Hours to days |
| Using a well‑built component library | Seconds |
If you’re working on Korean government projects, HANUI provides KRDS‑compliant React components out of the box.
npx hanui init
npx hanui add button input select form-field header modal
5 minutes to set up. Spend the rest of your time on business logic.
GitHub:
Documentation
