Skip to main content

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 from apps/web/components/ to the shared package? Recommended trigger: when apps/landing-hr or apps/landing-ba needs the same component (currently duplicating landing organisms).