A modern, reusable Angular 21 component library and design system for building enterprise-grade web applications. Built with standalone components, reactive forms, and Tailwind CSS v4.
Shade UI Kit is a comprehensive collection of production-ready UI components designed to accelerate development. Instead of building from scratch, import pre-built components with:
- ✅ Complete styling (light & dark mode)
- ✅ Full accessibility (WCAG AA)
- ✅ Type-safe TypeScript
- ✅ Responsive design
- ✅ Error handling & loading states
| Category | Components |
|---|---|
| Forms | Input, Textarea, Select, MultiSelect, Checkbox, Toggle, RadioGroup, DatePicker, ComboBox, ButtonGroup |
| Data Display | Table (with pagination/sorting/filtering), Tabs, Accordion, Calendar, Masonry |
| Feedback | Toast, Modal, Tooltip, Popover, Alert, Skeleton, LoadingOverlay, Spinner |
| Layout | PageContainer, Grid, Stack, AppShell, Sidebar, Header, Breadcrumb, PageHeader |
| Atoms | Badge, Avatar, Card, Chip, Divider, EmptyState, Progress, Button |
- Node.js 20.x or higher
- pnpm 9.x (package manager)
- Angular CLI 21.2.13+
# Clone the repository
git clone <repository-url>
cd shade-uikit
# Install dependencies
pnpm install
# Start development server
ng serve
# Navigate to http://localhost:4200/Simple Input Fields:
<sk-input
label="Email"
type="email"
placeholder="user@example.com"
formControlName="email"
/>
<sk-textarea
label="Description"
[rows]="4"
placeholder="Enter text..."
formControlName="description"
/>
<sk-input
label="Password"
type="password"
placeholder="••••••••"
formControlName="password"
/>Selection Controls:
<!-- Single Select -->
<sk-select
label="Status"
[options]="statusOptions"
formControlName="status"
/>
<!-- Multi-Select with Chips -->
<sk-multi-select
label="Tags"
[loadFn]="loadTags"
formControlName="tags"
/>
<!-- Checkbox -->
<sk-checkbox
label="I agree to terms"
formControlName="agreeTerms"
/>
<!-- Toggle -->
<sk-toggle
label="Enable notifications"
formControlName="notificationsEnabled"
/>
<!-- Date Picker -->
<sk-date-picker
label="Birth Date"
formControlName="birthDate"
/>Table with Server-Side Operations:
protected readonly columns: ColumnDef[] = [
{ key: 'name', label: 'Name', sortable: true, searchable: true },
{ key: 'email', label: 'Email', searchable: true },
{ key: 'status', label: 'Status', filterable: true },
{ key: 'createdDate', label: 'Created', sortable: true },
];
protected readonly loadFn = (params: TableParams) => {
return this.http.get('/api/users', { params });
};<sk-table
[columns]="columns"
[loadFn]="loadFn"
[clickable]="true"
(rowClick)="onRowClick($event)"
/>Tabs:
<sk-tabs>
<sk-tab label="Overview">
<p>Overview content</p>
</sk-tab>
<sk-tab label="Details">
<p>Details content</p>
</sk-tab>
</sk-tabs>Accordion:
<sk-accordion>
<sk-accordion-item title="Section 1">
Content for section 1
</sk-accordion-item>
<sk-accordion-item title="Section 2">
Content for section 2
</sk-accordion-item>
</sk-accordion>Toast Notifications:
private readonly toast = inject(ToastService);
onSuccess() {
this.toast.success('Operation completed!');
}
onError() {
this.toast.error('An error occurred', { title: 'Error' });
}
onWarning() {
this.toast.warning('Please review', { title: 'Warning' });
}
onInfo() {
this.toast.info('For your information');
}
// With custom duration (ms):
onPersistent() {
this.toast.error('Critical failure', { title: 'Error', duration: 0 }); // 0 = never auto-dismiss
}Modal Dialog:
constructor(private modal: ModalService) {}
openDialog() {
this.modal.open(MyDialogComponent, {
title: 'Confirm Action',
data: { message: 'Are you sure?' }
}).subscribe(result => {
if (result) {
// User confirmed
}
});
}Alert Banner:
<sk-alert variant="info" [dismissible]="true">
This is an informational message
</sk-alert>
<sk-alert variant="success">
Operation was successful!
</sk-alert>
<sk-alert variant="warning">
Please be careful
</sk-alert>
<sk-alert variant="error">
Something went wrong
</sk-alert>Page Container:
<sk-page-container maxWidth="2xl" gap="lg">
<!-- Your page content -->
</sk-page-container>Responsive Grid:
<!-- 1 column on mobile, 2 on tablet, 4 on desktop -->
<sk-grid [cols]="1" [colsSm]="2" [colsLg]="4" gap="md">
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
<div>Item 4</div>
</sk-grid>Flexible Stack:
<!-- Vertical stack (default) -->
<sk-stack gap="md">
<div>Item 1</div>
<div>Item 2</div>
</sk-stack>
<!-- Horizontal stack -->
<sk-stack direction="horizontal" justify="between" align="center">
<div>Left</div>
<div>Right</div>
</sk-stack>Badge:
<sk-badge variant="success">Active</sk-badge>
<sk-badge variant="warning">Pending</sk-badge>
<sk-badge variant="error">Failed</sk-badge>Avatar:
<sk-avatar name="John Doe" size="lg" />
<sk-avatar [image]="imageUrl" status="online" />Card:
<sk-card>
<sk-card-header title="Card Title" />
<sk-card-content>
Your content here
</sk-card-content>
<sk-card-footer>
Footer content
</sk-card-footer>
</sk-card>Light Mode:
- Primary:
#135bec - Background:
#f6f6f8 - Surface:
#ffffff - Border:
#e0e0e0 - Text:
#1f2937
Dark Mode:
- Background:
#0a0f18 - Surface:
#161b26 - Border:
#232936 - Text:
#f3f4f6
- Font Family: Inter (400, 500, 600, 700, 800, 900)
- Icons: Material Symbols Outlined
- Responsive: Auto-scales from mobile to desktop
// Toggle dark mode
const html = document.documentElement;
html.classList.toggle('dark');
// Or set specific theme
html.classList.add('dark'); // Dark mode
html.classList.remove('dark'); // Light modeAll components automatically adapt to dark mode!
The kit includes global HTTP error handling via a functional interceptor wired into the HTTP client:
// app.config.ts
provideJwt({ ... }, [httpErrorInterceptor])Errors automatically show as toasts with:
- ✅ Clear error messages from RFC 7807
problem+jsonresponses - ✅ Field-specific violations (e.g.
Email: must not be blank) - ✅ Support reference IDs for internal server errors
- ✅ Trace IDs for debugging
- ✅ Duration scaled by severity (500s show longer than 400s)
No need to handle errors per-component — httpErrorInterceptor catches everything!
protected readonly form = this.fb.group({
name: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
age: ['', [Validators.required, Validators.min(18)]],
terms: [false, Validators.requiredTrue],
});
protected onSubmit() {
if (this.form.valid) {
console.log(this.form.value);
}
}Built-in support for backend pagination, sorting, and filtering:
// Table automatically handles:
// ✅ Pagination
// ✅ Column sorting
// ✅ Field searching
// ✅ Multi-field filtering
// ✅ Loading statesJust pass your API endpoint and the table handles everything!
Every component is standalone (standalone: true) for:
- Better tree-shaking
- Cleaner imports
- Smaller bundles
- Flexible composition
All components use ChangeDetectionStrategy.OnPush for:
- Better performance
- Predictable reactivity
- Signal-based state management
Full type safety with:
- Properly typed inputs/outputs
- No
anytypes - JSDoc documentation
- IDE autocomplete
| Browser | Version |
|---|---|
| Chrome | Latest |
| Firefox | Latest |
| Safari | Latest |
| Edge | Latest |
- Bundle Size: ~45KB gzipped (all components)
- Load Time: < 1s on 4G
- Change Detection: OnPush (optimal performance)
- Tree-Shaking: Only import what you use
✅ Keyboard navigation ✅ Screen reader support ✅ ARIA labels & roles ✅ Focus management ✅ Color contrast compliance
Want to add a component or improve the kit?
- Create a component in
src/app/components/ - Use standalone architecture
- Add WCAG AA accessibility
- Document with JSDoc
- Test at mobile/tablet/desktop
- Use Tailwind utilities
MIT