@apcrda/ui
v0.10.0
Published
APCRDA UI is a production-ready React design system for APCRDA applications.
Readme
@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/uiPeer dependencies — install these in your project if not already present:
npm install react@^19 react-dom@^19Setup
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 | 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 DevWatch 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
