Component Inventory
title: Component Inventory owner: vizu last-updated: 2026-05-16 supersedes: docs/frontend/COMPONENT-INVENTORY.md (2026-02-20) status: canonical
Bilko Component Inventory
Organized by Atomic Design hierarchy (Brad Frost). All components live in apps/web/. Verified against filesystem as of 2026-05-16.
Atoms
The smallest indivisible UI units. Wrap a single HTML element or a Radix UI primitive. All shadcn/ui atoms use cva for variant composition and cn() for class merging.
shadcn/ui Atoms (in components/ui/)
| Component | Radix primitive | Notes |
|---|---|---|
button.tsx |
@radix-ui/react-slot |
6 variants (default, destructive, outline, secondary, ghost, link), 4 sizes. asChild for polymorphic rendering |
input.tsx |
native <input> |
Accepts aria-invalid, aria-describedby for RHF integration |
label.tsx |
@radix-ui/react-label |
Associates with input via htmlFor |
textarea.tsx |
native <textarea> |
Same aria pattern as Input |
select.tsx |
@radix-ui/react-select |
Keyboard nav + ARIA listbox built-in. Do not use native <select> |
badge.tsx |
native <span> |
Invoice status, market labels |
separator.tsx |
@radix-ui/react-separator |
Semantic <hr> with ARIA role |
skeleton.tsx |
native <div> |
Pulse animation for loading states. Use instead of spinner for content areas |
avatar.tsx |
@radix-ui/react-avatar |
User initials fallback when image unavailable |
Custom Atoms
| Component | File | Notes |
|---|---|---|
| LocaleSwitcher | components/locale-switcher.tsx |
sr-Latn / hr / bs toggle. aria-label required |
| TrialBanner | components/TrialBanner.tsx |
Subscription trial countdown notice |
Molecules
Atoms composed into a functional unit. May contain local useState. No external data fetching.
shadcn/ui Molecules
| Component | Radix primitive | Notes |
|---|---|---|
dialog.tsx |
@radix-ui/react-dialog |
Focus trap on open, ESC dismiss, scroll lock. Use for all modal flows |
dropdown-menu.tsx |
@radix-ui/react-dropdown-menu |
Keyboard nav, ARIA role="menu". Use for action menus, not navigation |
tabs.tsx |
@radix-ui/react-tabs |
ARIA role="tabpanel". Use for report views, settings sections |
sheet.tsx |
@radix-ui/react-dialog |
Slide-in side panel. Built on Dialog primitive |
toast.tsx |
custom | role="status" for informational toasts, role="alert" for errors |
card.tsx |
native | CardHeader, CardContent, CardTitle sub-components. Standard container |
table.tsx |
native <table> |
Always include <caption> or aria-label for screen readers |
Custom Molecules
| Component | File | Notes |
|---|---|---|
| TwoFactorDialog | components/settings/TwoFactorDialog.tsx |
2FA TOTP setup and verification flow |
| BetaInterestDialog | components/beta-interest-dialog.tsx |
Waitlist capture dialog |
| CreditNoteModal | components/CreditNoteModal.tsx |
Credit note creation (uses Dialog) |
| UpsellModal | components/UpsellModal.tsx |
Plan upgrade prompt |
| ComplianceWidget | components/ComplianceWidget.tsx |
Per-market compliance status badge |
| ChatMessage | components/chatbot/ChatMessage.tsx |
AI chatbot message bubble |
| ChatInput | components/chatbot/ChatInput.tsx |
AI chatbot text input with send |
Organisms
Complex UI sections with their own data dependencies. May call Zustand stores or receive server-fetched data as props.
| Component | File | Data source | Notes |
|---|---|---|---|
| Sidebar | components/sidebar.tsx |
useAuthStore |
Dark plum navigation, active route highlighting, RBAC-aware nav items |
| TopBar | components/top-bar.tsx |
useAuthStore |
User avatar, locale switcher, notification bell |
| ChatWidget | components/chatbot/ChatWidget.tsx |
Anthropic SDK | Floating AI accounting assistant |
| PausalTeaserCalculator | components/pausal/PausalTeaserCalculator.tsx |
local state | Tax estimate calculator for pausal businessmen |
Landing Page Organisms (components/landing/)
| Component | File |
|---|---|
| Navbar | components/landing/navbar.tsx |
| Hero | components/landing/hero.tsx |
| Features | components/landing/features.tsx |
| Testimonials | components/landing/testimonials.tsx |
| Pricing | components/landing/pricing.tsx |
| Footer | components/landing/footer.tsx |
Templates
Route layouts that define the page skeleton. No data logic — only layout composition.
| Template | File | Notes |
|---|---|---|
| Dashboard Layout | app/(dashboard)/layout.tsx |
Sidebar + TopBar + <main id="main-content"> |
| Legal Layout | app/(legal)/layout.tsx |
Simple centered layout, no sidebar |
| Root Layout | app/layout.tsx |
<html lang>, font loading, global providers (QueryProvider, AuthProvider) |
Composition Patterns
Pattern 1 — Icon-only Button
Every icon-only button MUST have aria-label. The icon itself must be aria-hidden.
<Button variant="ghost" size="icon" aria-label="Ukloni stavku">
<X className="h-4 w-4" aria-hidden="true" />
</Button>
Pattern 2 — RSC + Client Boundary Split
Keep data fetching in the RSC parent; interactive behavior in the Client child.
// RSC parent — no "use client"
export default async function InvoiceDetailPage({ params }: { params: { id: string } }) {
const invoice = await fetchInvoice(params.id)
return <InvoiceDetailClient invoice={invoice} />
}
// Client child
;('use client')
export function InvoiceDetailClient({ invoice }: { invoice: Invoice }) {
const [status, setStatus] = useState(invoice.status)
// interactive status changes, optimistic updates
}
Pattern 3 — Loading Skeleton via Suspense
export default function InvoicesPage() {
return (
<Suspense fallback={<TableSkeleton rows={10} />}>
<InvoiceTableServer />
</Suspense>
)
}
Pattern 4 — cva Custom Variant
import { cva } from 'class-variance-authority'
const statusBadge = cva('inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium', {
variants: {
status: {
paid: 'bg-green-100 text-green-800',
pending: 'bg-yellow-100 text-yellow-800',
overdue: 'bg-red-100 text-red-800',
draft: 'bg-gray-100 text-gray-600',
},
},
})
export function InvoiceStatusBadge({ status }: { status: InvoiceStatus }) {
return <span className={statusBadge({ status })}>{statusLabel[status]}</span>
}
OPEN QUESTION OQ-4:
packages/ui/is an empty scaffold. When should components graduate fromapps/web/components/to the shared package? Recommended trigger: whenapps/landing-hrorapps/landing-baneeds the same component (currently duplicating landing organisms).