Skip to content

Frontend Overview

CruzJS ships a frontend stack built on React (via React Router v7), Chakra UI for complex interactive components, and Tailwind CSS for utility-first styling. The result is a flexible system where you reach for the right tool depending on the complexity of what you are building.

LayerTechnologyPurpose
Routing & SSRReact Router v7File-based routes, loaders, actions, server-side rendering
Data fetchingtRPC + React QueryType-safe API calls, caching, optimistic updates
Complex componentsChakra UIModals, toasts, form controls, accessibility primitives
Utility stylingTailwind CSSLayout, spacing, typography, color, responsive design
Component library@cruzjs/uiPre-built cards, headers, state components

CruzJS follows a dual-styling approach rather than committing exclusively to one CSS strategy:

  • Tailwind CSS handles the majority of styling: layout, spacing, typography, colors, borders, and responsive breakpoints. Every component uses Tailwind utility classes directly in JSX.
  • Chakra UI is used selectively for components that benefit from built-in accessibility, animation, and state management: modals, toasts, spinners, form controls, and disclosure patterns.

This means you will rarely write custom CSS files. Instead, you compose styles inline:

// Tailwind for layout and visual styling
<div className="flex items-center gap-4 rounded-xl border border-slate-200 bg-white p-6">
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">
Section Title
</h3>
</div>
// Chakra for interactive components with built-in accessibility
import { Modal, ModalOverlay, ModalContent, useDisclosure } from '@chakra-ui/react';
const { isOpen, onOpen, onClose } = useDisclosure();

Frontend code lives in two main locations:

The @cruzjs/ui package exports reusable, framework-level components used across the dashboard, org pages, and settings views:

packages/ui/src/components/
StatCard.tsx # Metric display card with icon and color
PageHeader.tsx # Page title with optional action button
SectionCard.tsx # Grouped content card with optional header
DetailRow.tsx # Label-value row with icon
ConfirmModal.tsx # Confirmation dialog (Chakra-based)
StateComponents.tsx # LoadingState, EmptyState, PermissionDenied
TabNavigation.tsx # Horizontal tab bar
OrgHeader.tsx # Organization banner with avatar and stats
ActionItem.tsx # Clickable list item with icon and description

Import them from the @cruzjs/ui package:

import { StatCard, PageHeader, SectionCard, LoadingState } from '@cruzjs/ui';

Application-specific pages and feature components live in the main app:

apps/web/src/
routes/ # React Router route modules (pages)
features/ # Feature-specific components and logic
trpc/ # tRPC client setup and router type
database/ # Drizzle schema and seed files

CruzJS uses a consistent set of Tailwind patterns across all components:

// Cards and containers
<div className="rounded-xl border border-slate-200 bg-white p-6">
// Section headings
<h3 className="text-sm font-semibold text-slate-700 uppercase tracking-wide">
// Body text
<p className="text-base text-slate-600">
// Muted/secondary text
<p className="text-xs text-slate-500 font-medium">
// Primary action buttons
<button className="px-4 py-2 bg-[#003DCC] text-white font-medium rounded-lg hover:bg-[#0031A3] transition-colors">
// Danger variant
<button className="px-4 py-2 bg-red-500 text-white font-medium rounded-lg hover:bg-red-600 transition-colors">

The primary brand color is #003DCC (a deep blue). The UI uses the Tailwind slate palette for neutral grays. Accent colors come from Tailwind’s built-in palettes:

UsageColor
Primary / brand#003DCC
Successemerald-500
Warningamber-500
Dangerred-500
Infocyan-500
Neutral backgroundsslate-50 through slate-100
Textslate-900 (primary), slate-500 (secondary)

Use Tailwind responsive prefixes for adaptive layouts:

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard icon={<UsersIcon />} label="Members" value={42} color="primary" />
<StatCard icon={<ActivityIcon />} label="Active" value={38} color="emerald" />
</div>

Every page follows the same data flow pattern:

  1. The component calls a tRPC procedure via React Query hooks (useQuery or useMutation).
  2. The tRPC client sends an HTTP request to /api/trpc/* with auth and org headers attached automatically.
  3. The server resolves the procedure, runs middleware, executes business logic, and returns data.
  4. React Query caches the response and triggers a re-render with the new data.
  5. The component renders using @cruzjs/ui components and Tailwind styling.
import { trpc } from '~/trpc/client';
import { StatCard, PageHeader, LoadingState } from '@cruzjs/ui';
export default function DashboardPage() {
const { data, isLoading } = trpc.dashboard.stats.useQuery();
if (isLoading) return <LoadingState size="xl" text="Loading dashboard..." />;
return (
<div>
<PageHeader title="Dashboard" description="Your project at a glance" />
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mt-6">
<StatCard icon={<UsersIcon />} label="Users" value={data.userCount} color="primary" />
<StatCard icon={<ChartIcon />} label="Revenue" value={`$${data.revenue}`} color="emerald" />
</div>
</div>
);
}
  • Components — the full @cruzjs/ui component library
  • tRPC Client — fetching and mutating data
  • Layouts — dashboard, org, and public layout system
  • Forms — form patterns with Zod validation