@apcrda/ui
@apcrda/ui
Production-ready React 19 component library for APCRDA government applications. 58 accessible components covering forms, data display, navigation, feedback, and layout — built on Tailwind CSS v4 with full dark-mode and theming support.
Features
- 58 components — forms, tables, modals, navigation, charts, and more
- Accessible — WCAG 2.1 AA target; keyboard navigation; ARIA semantics via Radix UI primitives
- Fully typed — complete TypeScript types exported for every component and prop
- Themeable — CSS custom properties,
data-themeattribute, light and dark modes out of the box - Tree-shakeable — ESM build with preserved module boundaries; only the components you import are bundled
- FormField integration — label, hint, and error wiring handled automatically via React context
- DataTable — sort, filter, paginate, pin, resize, reorder, virtual scroll (1 000+ rows), CSV/Excel export
Installation
npm install @apcrda/ui
Peer dependencies — install these in your project if not already present:
npm install react@^19 react-dom@^19
Setup
1. Import the stylesheet
Add this once in your application entry point (e.g. main.tsx):
import '@apcrda/ui/styles.css'
2. Wrap with ThemeProvider
import { ThemeProvider } from '@apcrda/ui'
export default function Root() {
return (
<ThemeProvider defaultTheme="light">
<App />
</ThemeProvider>
)
}
ThemeProvider sets the data-theme attribute on <html> and persists the user's preference to localStorage automatically.
Usage
Basic form
import { Button, FormField, Input, PasswordInput, Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@apcrda/ui'
export function LoginForm() {
return (
<form className="flex flex-col gap-4">
<FormField label="Employee ID" required>
<Input placeholder="EMP-0001" />
</FormField>
<FormField label="Password" required>
<PasswordInput placeholder="Enter password" />
</FormField>
<FormField label="Department">
<Select>
<SelectTrigger>
<SelectValue placeholder="Select department" />
</SelectTrigger>
<SelectContent>
<SelectItem value="revenue">Revenue</SelectItem>
<SelectItem value="survey">Survey</SelectItem>
</SelectContent>
</Select>
</FormField>
<Button type="submit">Sign in</Button>
</form>
)
}
FormField with validation error
FormField wires label, hint text, and error message to the input automatically — no manual id, aria-describedby, or aria-invalid threading required.
<FormField
label="Aadhaar Number"
hint="12-digit number printed on your Aadhaar card"
error="Aadhaar number must be 12 digits"
required
>
<Input placeholder="XXXX XXXX XXXX" />
</FormField>
Government ID inputs
import { MaskedInput, PhoneInput, OtpInput, PinInput, CurrencyInput } from '@apcrda/ui'
// Aadhaar — auto-formats to XXXX XXXX XXXX
<MaskedInput mask="aadhaar" value={aadhaar} onChange={(e) => setAadhaar(e.target.value)} />
// PAN — auto-uppercases to AAAAA9999A
<MaskedInput mask="pan" value={pan} onChange={(e) => setPan(e.target.value)} />
// IFSC — auto-uppercases to AAAA0XXXXXX
<MaskedInput mask="ifsc" value={ifsc} onChange={(e) => setIfsc(e.target.value)} />
// Phone — +91 badge, emits raw 10-digit string
<PhoneInput value={phone} onChange={setPhone} />
// OTP — 6 visible digit cells
<OtpInput value={otp} onChange={setOtp} />
// PIN — 4 masked cells
<PinInput value={pin} onChange={setPin} />
// Currency — ₹ prefix, formats as ₹1,23,456.00 on blur
<CurrencyInput value={amount} onChange={setAmount} />
DataTable
import { DataTable } from '@apcrda/ui'
import type { ColumnDef } from '@apcrda/ui'
type Application = {
id: string
applicantName: string
status: string
submittedOn: string
}
const columns: ColumnDef<Application>[] = [
{ accessorKey: 'id', header: 'Application ID', size: 160 },
{ accessorKey: 'applicantName', header: 'Applicant Name', size: 260 },
{ accessorKey: 'status', header: 'Status', size: 160 },
{ accessorKey: 'submittedOn', header: 'Submitted On', size: 160 },
]
export function ApplicationsTable({ data }: { data: Application[] }) {
return (
<DataTable
data={data}
columns={columns}
pagination
selectable
columnFilters
exportCsv
stickyHeader
/>
)
}
Notifications
import { Button } from '@apcrda/ui'
import { useToast } from '@apcrda/ui'
export function SaveButton() {
const { toast } = useToast()
return (
<Button
onClick={() =>
toast({ title: 'Saved', description: 'Application updated successfully.', variant: 'success' })
}
>
Save
</Button>
)
}
Components
Form & Input
| Component | Description |
|---|---|
Input |
Text input — sizes sm/md/lg, left/right icons, loading spinner |
Textarea |
Multi-line input with optional character counter |
PasswordInput |
Password field with show/hide toggle |
NumberInput |
Numeric stepper with min, max, step |
CurrencyInput |
₹ prefix, Indian locale (en-IN) comma formatting on blur |
SearchInput |
Locked Search icon, X clear button, loading spinner |
EmailInput |
Locked Mail icon, type="email" |
UrlInput |
Locked Link2 icon, type="url" |
PhoneInput |
Fixed +91 badge, 10-digit field, auto-space after 5th digit |
OtpInput |
6-cell OTP — auto-advance, backspace, paste support |
PinInput |
4-cell masked PIN — same keyboard behaviour as OTP |
MaskedInput |
Government ID masks: Aadhaar, PAN, IFSC, Date (DD/MM/YYYY) |
SignaturePad |
Canvas signature with mouse + touch drawing and clear button |
FileUpload |
File picker — button or drag-and-drop dropzone variant |
Checkbox |
Accessible checkbox with indeterminate state |
RadioGroup |
Controlled radio button group |
Switch |
Toggle switch |
Select |
Single-value dropdown |
MultiSelect |
Multi-value select with chips, count, or first+count summary |
Combobox |
Searchable single-select dropdown |
Autocomplete |
Free-text input with suggestions list |
DatePicker |
Single-date calendar picker |
DateRangePicker |
Date range calendar picker |
FormField |
Label + input + hint + error wrapper (context-based) |
Label |
Accessible form label |
Data Display
| Component | Description |
|---|---|
DataTable |
Full-featured table with 30+ capabilities |
StatCard |
KPI metric card with trend indicator |
Calendar |
Standalone month calendar |
Timeline |
Vertical event timeline |
ScrollArea |
Styled scrollable container |
Progress |
Linear and indeterminate progress bar |
Skeleton |
Loading placeholder shimmer |
Navigation
| Component | Description |
|---|---|
Navbar |
Top application bar |
Sidebar |
Collapsible side navigation with nested items |
Breadcrumb |
Page hierarchy trail |
Tabs |
Horizontal tab panels |
Pagination |
Page navigation controls |
Stepper |
Multi-step workflow indicator |
Accordion |
Expand/collapse content sections |
Feedback & Overlay
| Component | Description |
|---|---|
Alert |
Inline status message — info, success, warning, error |
AlertDialog |
Blocking confirmation modal with cancel/confirm actions |
Dialog |
General-purpose modal dialog |
Drawer |
Slide-in side panel |
Toast |
Ephemeral notification — top-right stack |
Tooltip |
Hover hint on any element |
Popover |
Anchored floating content panel |
NotificationCenter |
Notification feed with read/unread state |
EmptyState |
Zero-data placeholder with icon and action |
Layout & Display
| Component | Description |
|---|---|
Card |
Elevated surface container with optional header and footer |
Divider |
Horizontal or vertical separator |
Typography |
Heading and Text variants with size and weight props |
Avatar |
User avatar with image or fallback initials |
Badge |
Status and count pill |
Tag |
Removable label chip |
Spinner |
Accessible loading indicator |
Kbd |
Keyboard shortcut key display |
Utility
| Component | Description |
|---|---|
CommandPalette |
⌘K command search overlay |
ContextMenu |
Right-click context menu |
Theming
Light and dark mode
APCRDA UI uses CSS custom properties scoped to a data-theme attribute:
<html data-theme="light">…</html>
<html data-theme="dark">…</html>
ThemeProvider manages the attribute and syncs it with localStorage:
// Force a specific theme
<ThemeProvider defaultTheme="dark">…</ThemeProvider>
// Follow system preference
<ThemeProvider defaultTheme="system">…</ThemeProvider>
Custom tokens
Override any design token after importing the stylesheet:
@import '@apcrda/ui/styles.css';
:root {
--apcrda-color-primary: #0052cc;
--apcrda-color-primary-foreground: #ffffff;
--apcrda-radius-md: 0.25rem;
--apcrda-font-sans: 'Noto Sans Telugu', system-ui, sans-serif;
}
Tailwind CSS plugin
If you use Tailwind CSS in your app, register the APCRDA tokens as a plugin so your own classes can reference the same variables:
// tailwind.config.ts
import apcrdaPlugin from '@apcrda/ui/plugin.css'
export default {
plugins: [apcrdaPlugin],
}
TypeScript
All components, props, and utility types are fully typed and exported from the package root:
import type {
ButtonVariant,
ColumnDef,
DataTableProps,
DataTableRowAction,
InputProps,
InputSize,
} from '@apcrda/ui'
The package ships .d.ts declaration files alongside the ESM bundle — no @types/ package needed.
Accessibility
- Target: WCAG 2.1 AA
- Keyboard navigation: all interactive components are fully keyboard-accessible
- Screen readers: semantic HTML and Radix UI ARIA patterns throughout
- Focus management: visible focus rings, focus trapping in modals and drawers
- Motion: animations respect
prefers-reduced-motion - Form errors: error messages are linked to inputs via
aria-describedbyand announced asrole="alert"
DataTable capabilities
| Feature | Prop |
|---|---|
| Client-side pagination | pagination |
| Server-side pagination | serverPagination |
| Infinite scroll | infiniteScroll |
| Global search | built-in |
| Column filters (text / select / range / date) | columnFilters |
| Multi-column sort | multiSort |
| Column visibility toggle | columnVisibility |
| Column resize | columnResize |
| Column pinning | columnPinning |
| Column reorder (drag and drop) | columnReorder |
| Row grouping | grouping |
| Row expand / sub-rows | getRowCanExpand + renderSubRow |
| Tree data | getSubRows |
| Row selection + bulk actions | selectable + bulkActions |
| Row actions menu | rowActions |
| Virtual scroll (1 000+ rows) | virtualize |
| CSV export | exportCsv |
| Excel export | exportExcel |
print |
|
| Copy to clipboard | copy |
| Density toggle (compact / normal / spacious) | density |
| Sticky header | stickyHeader |
| Striped rows | striped |
| Loading skeleton | loading |
| Empty state | emptyMessage / emptyDescription |
| Footer aggregations | column footer + aggregationFn |
Bundle size
The package is built as ESM with Vite in library mode. Unused components are tree-shaken automatically by your bundler. Runtime dependencies (Radix UI primitives, CVA, date-fns, etc.) are bundled. React and ReactDOM are peer dependencies and are not included.
Deployment (CI/CD)
Storybook is deployed as a Docker container to the applications VM and served at
https://ui.theamaravaticity.com behind host Nginx with a Let's Encrypt
certificate. The whole flow is driven by GitLab CI (.gitlab-ci.yml).
Deployment flow
- test — split into parallel jobs:
test:unit— executes linting, typechecking, and standard JSDOM unit tests.test:storybook— executes browser-based interaction tests inside a Playwright container.
- publish:npm — bumps the patch version and publishes
@apcrda/ui(skips if the version already exists). - dockerize — builds the image and pushes
:latest+:<commit-sha>to the GitLab Container Registry. - deploy — runs
docker/deploy.sh, which:- records the currently running image tag (the rollback target),
- validates the compose config (
docker compose config -q), - starts the new image with
docker compose up -d, - health-checks the published port,
- rolls back automatically if the health check fails,
- prunes old images, keeping the last few for rollback.
The deploy job uses resource_group: production, so only one deployment can run
at a time.
How to deploy
Just push to Dev:
git push origin Dev
Watch the pipeline in GitLab (CI/CD → Pipelines). A green deploy job means
the new version is live and healthy. A red deploy job means the change was
rejected and the previous version was restored automatically (see below).
How rollback works
Automatic — built into deploy.sh. If the freshly deployed image does not
pass its health check within the timeout, the script restores the previously
running image, re-checks it, and then fails the pipeline with a clear error.
No manual action is required; the live site stays on the last known-good build.
Manual — to pin an older build (e.g. a bad image that still passed its health
check), run docker/rollback.sh on the VM from the repo's docker/ directory:
# list images that are available locally on the host
docker images gitlab.apcrda.org:5050/crda/apcrda-design-system
# roll back to a specific commit SHA
./rollback.sh <git-short-sha>
The last KEEP_IMAGES (default 5) SHA-tagged images are always kept on the host
so a recent rollback target is available.
Tuning
deploy.sh / rollback.sh read these environment variables (with sensible
defaults), so nothing is hard-coded:
| Variable | Default | Purpose |
|---|---|---|
HEALTH_RETRIES |
10 |
Health-check attempts before failing |
HEALTH_INTERVAL |
3 |
Seconds between attempts |
KEEP_IMAGES |
5 |
SHA-tagged images retained for rollback |
HOST_PORT |
8203 |
Published container port |
DEPLOY_HOST |
10.247.16.113 |
Host the health check targets |
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
deploy job red, log shows "rolled back to … (now healthy)" |
New image failed its health check; site is safely on the previous build. Check the app/build for the failing commit, fix, and push again. |
deploy job red, "rollback … is unhealthy" |
Both new and previous images are unhealthy. SSH to the VM and inspect: docker logs apcrda-storybook. Manually roll back to a known-good SHA with ./rollback.sh <sha>. |
| Health check never passes | Confirm the container is up: docker ps. Check it serves on the port: curl -I http://127.0.0.1:8203/. Review docker logs apcrda-storybook. |
Pipeline stuck pending |
The deploy resource_group is busy with another run, or no runner is available. Check CI/CD → Pipelines and the runner status. |
docker compose: not found in deploy |
The deploy job installs docker-cli-compose; confirm the before_script ran. |
| Site shows old version after green deploy | Hard-refresh / clear CDN-or-browser cache. Verify the live tag: docker inspect --format '{{.Config.Image}}' apcrda-storybook. |
Links
License
MIT APCRDA