Skip to main content

UI Component Library

Overview

The Ever Works UI component library provides a set of reusable, themed primitives used throughout the web dashboard. All components live in apps/web/src/components/ui/ and follow consistent patterns: Tailwind CSS 4 with custom design tokens, dark mode support via the dark: prefix, and the cn() utility (wrapping clsx + tailwind-merge) for conditional class composition. Components are client components only when interactivity is required.

Architecture

apps/web/src/components/ui/
├── button.tsx # Button with variants, sizes, link support
├── dialog.tsx # Dialog system (HeadlessUI)
├── input.tsx # Text input with label, error, helper
├── tooltip.tsx # CSS-only tooltip
├── collapsible-card.tsx # Expandable card
├── checkbox.tsx # Checkbox input
├── select.tsx # Select dropdown
├── switch.tsx # Toggle switch
├── textarea.tsx # Multi-line text input
├── auto-resize-textarea.tsx # Auto-growing textarea
├── dropdown-menu.tsx # Dropdown menu
├── show-datetime.tsx # Date/time display
├── top-loader.tsx # Page transition loader
└── DotLottiePlayer.tsx # Lottie animation player

All components use forwardRef where applicable and spread remaining props onto the underlying HTML element for maximum composability.

Components

Button

File: apps/web/src/components/ui/button.tsx

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'ghost' | 'danger' | 'unstyled''primary'Visual style variant
size'sm' | 'md' | 'lg' | 'icon''md'Button size
hrefstring (optional)-Renders as Next.js Link instead of <button>
loadingboolean (optional)falseShows a spinner and disables the button
fullWidthboolean (optional)falseStretches to full container width
classNamestring (optional)-Additional CSS classes

Plus all standard ButtonHTMLAttributes.

Variants:

VariantAppearance
primarySolid primary brand color background, white text
secondaryBordered with surface background, muted text
ghostTransparent background, subtle hover effect
dangerRed background, white text for destructive actions
unstyledNo default styles, fully custom

Sizes:

SizePaddingText
smpx-3 py-1.5text-xs
mdpx-4 py-2text-sm
lgpx-6 py-3text-base
iconp-2Square button for icon-only

When href is provided, the button renders as a Next.js Link component with the same visual styling. When loading is true, a spinning SVG replaces the button content and the button is disabled.

import { Button } from '@/components/ui/button';

<Button variant="primary" size="md">Save Changes</Button>
<Button variant="danger" loading={isPending}>Delete</Button>
<Button variant="ghost" size="icon"><Trash2 className="w-4 h-4" /></Button>
<Button href="/dashboard" variant="secondary">Go to Dashboard</Button>

Dialog

File: apps/web/src/components/ui/dialog.tsx

Built on HeadlessUI's Dialog component, this module exports several sub-components:

ExportDescription
DialogRoot dialog wrapper (controls open/close state)
DialogContentCentered modal panel with backdrop, scale transition, and rounded corners
DialogHeaderFlex container for the title area
DialogTitleRe-exported HeadlessUI DialogTitle
DialogDescriptionMuted text below the title
DialogFooterRight-aligned flex container for action buttons
DialogCloseClose button (X icon) in the top-right corner

DialogContent applies a scale + fade transition on open/close using HeadlessUI's Transition:

  • Enter: opacity-0 scale-95 to opacity-100 scale-100 over 200ms.
  • Leave: reverse over 150ms.

The backdrop is a semi-transparent black overlay (bg-black/50).

Prop (DialogContent)TypeDescription
childrenReactNodeDialog body content
classNamestring (optional)Additional classes for the panel
Prop (DialogClose)TypeDescription
onClose() => voidCallback when close button is clicked
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from '@/components/ui/dialog';

<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogContent>
<DialogClose onClose={() => setIsOpen(false)} />
<DialogHeader>
<DialogTitle>Confirm Action</DialogTitle>
</DialogHeader>
<p>Are you sure you want to proceed?</p>
<DialogFooter>
<Button variant="secondary" onClick={() => setIsOpen(false)}>
Cancel
</Button>
<Button onClick={handleConfirm}>Confirm</Button>
</DialogFooter>
</DialogContent>
</Dialog>;

Input

File: apps/web/src/components/ui/input.tsx

PropTypeDefaultDescription
labelstring (optional)-Label text above the input
errorstring (optional)-Error message below the input
helperTextstring (optional)-Helper text (hidden when error is present)
variant'default' | 'form''default'Style variant

Plus all standard InputHTMLAttributes.

Variants:

VariantCharacteristics
defaultLarger padding (py-3), focus ring (ring-2 ring-primary/20), hover border transition
formCompact padding (py-2), no focus ring, minimal styling for dense forms

The component auto-generates an id via React's useId hook if none is provided, ensuring the <label> is properly associated with the <input>. Error state changes the border to border-danger/50 and the focus ring to ring-danger/20.

import { Input } from '@/components/ui/input';

<Input
label="Username"
placeholder="Enter username"
error={errors.username}
helperText="Must be at least 3 characters"
/>

<Input variant="form" label="API Key Name" maxLength={100} />

Tooltip

File: apps/web/src/components/ui/tooltip.tsx

PropTypeDefaultDescription
contentstring-Tooltip text
position'top' | 'bottom' | 'left' | 'right''top'Tooltip position relative to trigger
childrenReactNode-Trigger element

A pure CSS tooltip that requires no JavaScript for show/hide. It uses Tailwind's group and group-hover utilities:

  • The wrapper is a relative group container.
  • The tooltip is absolutely positioned and hidden by default (opacity-0 invisible).
  • On hover, it transitions to opacity-100 visible.

Position classes:

PositionPlacement
topAbove, centered horizontally
bottomBelow, centered horizontally
leftLeft side, centered vertically
rightRight side, centered vertically
import { Tooltip } from '@/components/ui/tooltip';

<Tooltip content="Create new work" position="bottom">
<Button variant="icon">
<Plus className="w-4 h-4" />
</Button>
</Tooltip>;

CollapsibleCard

File: apps/web/src/components/ui/collapsible-card.tsx

PropTypeDefaultDescription
titlestring-Card header text
defaultOpenboolean (optional)falseWhether the card starts expanded
headerContentReactNode (optional)-Additional content in the header row
actionsReactNode (optional)-Action buttons in the header (click propagation stopped)
childrenReactNode-Collapsible body content

A card with a clickable header that toggles the visibility of its body content. The chevron icon rotates 180 degrees when expanded. The actions slot uses stopPropagation so clicking action buttons does not toggle the card.

import { CollapsibleCard } from '@/components/ui/collapsible-card';

<CollapsibleCard
title="OpenAI Settings"
headerContent={<span className="text-xs text-text-muted">v1.2.0</span>}
actions={<Switch checked={enabled} onChange={toggle} />}
defaultOpen
>
<div className="space-y-4">
<Input label="API Key" type="password" />
<Input label="Model" value="gpt-4o" />
</div>
</CollapsibleCard>;

Implementation Details

The cn() Utility

All UI components use cn() from @/lib/utils for class composition:

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

This combines clsx (for conditional classes) with tailwind-merge (for deduplicating and resolving Tailwind class conflicts). For example, if a parent passes className="py-4" but the component has py-3, twMerge ensures only the override applies.

ForwardRef Pattern

Button and Input use React.forwardRef so parent components can attach refs for focus management or form libraries:

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ variant, size, ...props }, ref) => {
return <button ref={ref} {...props} />;
});

HeadlessUI Integration

The Dialog system uses HeadlessUI v2 for accessible modal behavior:

  • Focus trapping within the dialog.
  • Escape key to close.
  • Click-outside-to-close via the backdrop.
  • Proper aria-* attributes on all dialog parts.

The Transition component handles enter/leave animations with configurable duration and easing.

Styling & Theming

Design Token Reference

TokenLightDarkUsage
surfaceWhiteDark grayPrimary backgrounds
surface-secondaryLight grayDarker grayCard/section backgrounds
surface-tertiaryLightest grayDarkest grayDisabled backgrounds
textNear blackNear whitePrimary text
text-mutedMedium grayLight graySecondary text
borderLight grayDark grayBorders and dividers
primaryBrand blueBrand blueAccent/action color
dangerRedRedDestructive actions
warningAmberAmberWarning states
successGreenGreenSuccess states
input-bg-dark-Specific dark input bgInput backgrounds in dark mode

Consistent Patterns

All form inputs follow this border pattern:

  • Default: border-primary/30 dark:border-primary/10
  • Focus: focus:border-primary
  • Error: border-danger/50 focus:border-danger
  • Disabled: disabled:bg-surface-tertiary disabled:cursor-not-allowed

All transitions use transition-colors or transition-all with duration-200 for smooth interactions.

Usage Examples

Form with Validation

'use client';

import { useState } from 'react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';

export function SettingsForm() {
const [errors, setErrors] = useState<Record<string, string>>({});

return (
<form className="space-y-4">
<Input label="Display Name" error={errors.name} helperText="This will be shown publicly" />
<Input variant="form" label="Website URL" type="url" error={errors.url} />
<Button type="submit" fullWidth>
Save Settings
</Button>
</form>
);
}

Confirmation Dialog

'use client';

import { useState } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogClose } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';

export function DeleteConfirmation({ onConfirm }) {
const [open, setOpen] = useState(false);

return (
<>
<Button variant="danger" onClick={() => setOpen(true)}>
Delete
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogClose onClose={() => setOpen(false)} />
<DialogHeader>
<DialogTitle>Delete Item</DialogTitle>
</DialogHeader>
<p className="text-sm text-text-muted">This action cannot be undone.</p>
<DialogFooter>
<Button variant="secondary" onClick={() => setOpen(false)}>
Cancel
</Button>
<Button variant="danger" onClick={onConfirm}>
Delete
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}