Component library
ShipVeryFast ships with a TypeScript component library built on shadcn/ui and Tailwind CSS, organized by domain and exposed through barrel exports. This page covers where the components live, how to import them, the validated form system, the providers that wrap the app, and how to add or override a shadcn/ui primitive.
The component directories
Everything lives under components/, grouped by concern. Each top-level group has its own index.ts barrel, and the root components/index.tsre-exports them all.
| Directory | What it holds |
|---|---|
components/ui/ | shadcn/ui primitives (button.tsx, card.tsx, input.tsx, select.tsx, tabs.tsx, table.tsx, etc.) plus the form system, analytics, admin, deployment and pricing sub-folders. |
components/ui/form/ | The validated form system: FormContainer, FormField, FormInput, FormButton, useFormValidation and Zod schemas. |
components/dashboard/ | The signed-in product surface: DashboardSidebar, DashboardTopbar, DataTable, CommandPalette, charts and more. |
components/landing/ | Marketing sections: Hero, Features, Pricing, FAQ, Testimonials, SiteFooter. |
components/auth/ | Authentication UI: AuthForm and AuthShell. |
components/admin/ | AdminSidebar for the admin area (richer admin tools live under components/ui/admin/). |
components/security/ | Security and compliance UI such as AuditTrail. |
components/providers/ | Context providers and performance optimizers: FeatureFlagsProvider, AnalyticsProvider, QueryProvider, LazyLoadProvider, and more. |
components/theme/ | Theming: ThemeProvider, ThemeToggle, ThemeSelector and the useTheme hook. |
components/examples/ | Reference implementations like AdvancedFormExample and FeatureFlagExample. |
Barrel imports and categorized groups
The root barrel @/components re-exports the most common components directly, so a single import line gets you UI primitives, providers and the theme system together.
// Direct named imports from the root barrel
import { Button, Card, ThemeProvider, FormContainer } from "@/components";
// Or import from a specific group barrel
import { Button, Card, Input, Select } from "@/components/ui";
import { FeatureFlagsProvider, AnalyticsProvider } from "@/components/providers";
import { ThemeProvider, ThemeToggle, useTheme } from "@/components/theme";Several barrels also expose categorized object groups for convenience. Incomponents/ui/index.ts these are real exported constants you can destructure:
import { FormControls, CardComponents } from "@/components/ui";
const { Input, Textarea, Select, Checkbox, Switch, Label, Button } = FormControls;
const { Card, CardHeader, CardTitle, CardContent } = CardComponents;components/index.ts declares placeholder groups such asUIComponents and FormComponents as empty constobjects, they are kept for documentation only. Prefer the populated groups incomponents/ui/index.ts (e.g. FormControls,CardComponents, AdminComponents) or direct named imports.Form system: FormContainer / FormField with validation and CSRF
The form system in components/ui/form/ pairs layout components (FormContainer, FormField, FormSubmitButton) with theuseFormValidation hook and a set of Zod schemas (loginSchema, signupSchema, profileSchema, and more).useFormValidation validates each field on change/blur against your schema and exposes values, errors, isSubmitting,handleChange, handleBlur and handleSubmit.
"use client";
import {
FormContainer,
FormField,
FormSubmitButton,
useFormValidation,
loginSchema,
} from "@/components/ui/form";
import { fetchWithCSRF } from "@/libs/useCSRF";
export function LoginForm() {
const form = useFormValidation({
schema: loginSchema,
onSubmit: async (data) => {
// fetchWithCSRF grabs a token from /api/csrf-token and
// sends it as the X-CSRF-Token header on the request.
await fetchWithCSRF("/api/auth/login", {
method: "POST",
body: JSON.stringify(data),
});
},
});
return (
<FormContainer variant="card" maxWidth="md" centered onSubmit={form.handleSubmit}>
<FormField
name="email"
label="Email"
type="email"
required
error={form.errors.email}
inputProps={{
value: form.values.email || "",
onChange: form.handleChange,
onBlur: form.handleBlur,
}}
/>
<FormField
name="password"
label="Password"
type="password"
required
error={form.errors.password}
inputProps={{
value: form.values.password || "",
onChange: form.handleChange,
onBlur: form.handleBlur,
}}
/>
<FormSubmitButton loading={form.isSubmitting}>Sign in</FormSubmitButton>
</FormContainer>
);
}FormContainer handles layout only, it accepts variant(default | card | inline), spacing,maxWidth and centered, and forwards the rest to a native<form>. FormField wires the label, the right input (FormInput, FormTextarea or FormSelect based ontype), the error/success message and the accessibility attributes (aria-describedby, aria-invalid).
CSRF protection
The form components do not inject CSRF tokens for you. CSRF lives inlibs/useCSRF.ts: the useCSRF() hook returnscsrfToken and a getAuthHeaders() helper, whilefetchWithCSRF(url, options) fetches a token from /api/csrf-tokenand attaches it as the X-CSRF-Token header on your request. Use one of those in your onSubmit for any mutating call. See the dedicated CSRF protection page for details.
Provider components and where they wrap the app
Cross-cutting state is provided through context. The app is wrapped once inapp/providers.tsx (a client component imported by the rootapp/layout.tsx), which composes the session, feature flags and theme providers:
// app/providers.tsx (simplified)
import { SessionProvider } from "next-auth/react";
import { FeatureFlagsProvider } from "@/components/providers/FeatureFlagsProvider";
import { ThemeProvider } from "@/components/theme/theme-provider";
export default function Providers({ children }) {
return (
<SessionProvider>
<FeatureFlagsProvider>
<ThemeProvider>{children}</ThemeProvider>
</FeatureFlagsProvider>
</SessionProvider>
);
}Because FeatureFlagsProvider and ThemeProvider already wrap the whole tree, you can consume them anywhere with their hooks, useFeatureFlags()and useTheme(), or use the FeatureGate helper to gate UI on a flag:
import FeatureGate from "@/components/ui/FeatureGate";
import { useTheme } from "@/components/theme";
function ThemedBeta() {
const { theme, isDarkMode, setTheme } = useTheme();
return (
<FeatureGate feature="beta-dashboard" fallback={<LegacyDashboard />}>
<BetaDashboard />
</FeatureGate>
);
}Additional providers in components/providers/ (AnalyticsProvider,QueryProvider, LazyLoadProvider, FontOptimizer,ResourceOptimizer) are available to compose where you need them. See Feature flags and Theming for the full hook APIs.
Conventions: TypeScript props, accessibility, JSDoc
- Typed props. Components export a typed props interface, primitives extend the native element attributes (e.g.
ButtonextendsButtonHTMLAttributes), and complex components export named prop types likeFormContainerPropsandFormFieldPropsfor reuse. - className merging. Components compose classes with the
cn()helper from@/libs/utils(clsx + tailwind-merge), so a passedclassNamealways wins over defaults. - Accessibility. Shared components wire ARIA by default,
FormFieldbuildsaria-describedby, setsaria-invalidon error and links labels viahtmlFor; buttons keep visible focus rings (focus:ring-2 focus:ring-ring). - JSDoc. Shared components carry JSDoc blocks with
@exampleusage right above the export, which surfaces in editor tooltips. - Dark mode. Styling uses Tailwind
dark:variants throughout so components respond to the theme without extra wiring.
Adding or overriding a shadcn/ui component
The project is configured for the shadcn/ui CLI via components.json(style: "new-york", baseColor: "neutral",iconLibrary: "lucide", RSC enabled). New primitives land incomponents/ui/:
# Add a shadcn/ui primitive into components/ui/
npx shadcn@latest add dialogTo override an existing primitive, edit its file directly, for example change the variant classes in components/ui/button.tsx, which definesvariant (default, primary, outline,destructive, ghost, link) and size(sm, md, lg, icon). After adding a new file, export it from components/ui/index.tsso it's reachable from the@/components and @/components/ui barrels.
cn() utility in this repo lives at @/libs/utils(note the plural), which is the import existing components use. The shadcn CLI'sutils alias in components.json points at @/lib/utils, so newly generated files may import from the wrong path, double-check thecn import after running npx shadcn add.Next steps
- Theming, the
ThemeProvideranduseThemeAPI. - Feature flags,
FeatureFlagsProvider,useFeatureFlagsandFeatureGate. - CSRF protection, securing form submissions with
fetchWithCSRF.
