Appearance
Webapp Components
Webapp components combine shared UI primitives with application layouts, domain behavior, and route composition. The @jubiloop/ui component API and styling contract are documented separately in UI Components Package.
Component Layers
| Layer | Location | Responsibility |
|---|---|---|
| Shared UI | packages/ui/src/components/ | Reusable, domain-agnostic controls and presentation |
| Webapp layouts | apps/webapp/src/components/layout/ | Page width, landmarks, headers, and route-state frames |
| Domain components | apps/webapp/src/components/ | Forms and compositions for auth, accounts, events, workspaces, and notifications |
| Route composition | apps/webapp/src/routes/ | Route-specific assembly, guards, loaders, pending UI, and error UI |
Shared UI component files use kebab-case. Webapp React component files use PascalCase, while hooks, constants, and other non-component files use kebab-case.
Shared UI Layer
Application code imports controls from package subpaths, not directly from Radix or shadcn sources:
tsx
import { Button } from '@jubiloop/ui/components/button'
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@jubiloop/ui/components/form'
import { Input } from '@jubiloop/ui/components/input'Move a component into @jubiloop/ui only when it is reusable and free of Jubiloop domain logic, API calls, route assumptions, and app-managed state. Package exports, component conventions, tokens, typography, and contrast requirements live in UI Components Package.
Layout Composition
Layout components give route families a consistent structure:
AppLayoutcombines the skip link, header slot, and main content area.PublicLayout,OnboardingLayout, andAuthenticatedFallbackLayoutprovide route-family frames.ContainerandPageHeaderprovide repeated page composition without embedding domain behavior.RouteLoadingState,AppHeaderFallback, and error fallback components preserve the same shell while route data is pending or unavailable.
The workspace route shows how route and app layers fit together:
tsx
function WorkspaceRoute() {
return (
<WorkspaceProvider>
<AppLayout header={<AppHeader showWorkspaceControls />}>
<Outlet />
</AppLayout>
</WorkspaceProvider>
)
}The route selects the provider and shell because they depend on workspace route data. AppLayout and AppHeader remain reusable across authenticated pages.
Domain Components
Domain directories group related UI and their component-level hooks:
| Directory | Examples |
|---|---|
components/auth/ | Sign-in, sign-up, reset-password, and credential layouts |
components/events/create/ | Event form composition and form-state hook |
components/workspace/ | Workspace switcher, creation, and settings dialogs |
components/account/ | Profile and password forms |
components/notification-bell/ | Notification list and popover behavior |
components/header/ | Responsive desktop and mobile header controls |
These components use configured app hooks rather than constructing API clients. For example, CreateEventForm delegates form setup and submission mapping to useCreateEventForm(), which calls the app-level useCreateEvent() mutation. The form renders fields and status; the hooks connect validation, active-workspace data, API errors, logging, and toasts.
Composition Rules
- Keep route guards, route loaders, route search validation, and route-state components in route modules. See Routing.
- Keep reusable webapp behavior beside its domain component or in
src/hooks/when several domains consume it. - Let React Hook Form control form inputs and use Zod schemas from
src/schemas/. - Keep component-only interaction state local. Current examples include the mobile header sheet, notification popover, workspace dialogs, and event calendar.
- Pass data and callbacks into presentational children rather than making each child rediscover the active route, workspace, or request state.
- Prefer a few focused components over one component that owns layout, data access, mutation side effects, and every visual state.
Accessibility Responsibilities
Shared controls provide the semantics and keyboard behavior of their native or Radix foundations. Webapp compositions must still provide meaningful labels, accessible names, state, focus order, and error feedback.
For forms, use FormLabel, FormControl, and FormMessage so descriptions and validation errors are associated with their controls. Test the rendered workflow with Testing Library role and label queries; component tests sit beside the relevant domain directory.
Styling Context
Webapp components use semantic classes exposed by @jubiloop/ui. App-only layout decisions stay in the webapp, while shared tokens, typography roles, base styles, class merging, and validation stay in the UI package. See UI Components Package for the full contract.