Skip to content

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

LayerLocationResponsibility
Shared UIpackages/ui/src/components/Reusable, domain-agnostic controls and presentation
Webapp layoutsapps/webapp/src/components/layout/Page width, landmarks, headers, and route-state frames
Domain componentsapps/webapp/src/components/Forms and compositions for auth, accounts, events, workspaces, and notifications
Route compositionapps/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:

  • AppLayout combines the skip link, header slot, and main content area.
  • PublicLayout, OnboardingLayout, and AuthenticatedFallbackLayout provide route-family frames.
  • Container and PageHeader provide 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:

DirectoryExamples
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.

Built with ❤️ by the Jubiloop team