@undefine-ui/design-system
v3.13.1
Published
Define design system React components and theme.
Downloads
831
Readme
@undefine-ui/design-system
React component library + MUI theme layer extracted from the Define design system.
Installation
pnpm add @undefine-ui/design-system
# peer deps you will already have in most React + MUI apps
pnpm add @mui/material @mui/x-data-grid @emotion/react @emotion/styled react react-dom react-hook-formNote: Fonts (Work Sans and Geist) are bundled with the package and automatically loaded by the ThemeProvider. You don't need to install or import @fontsource packages separately.
Usage
Providers
Every consumer app needs the same provider stack that the showcase uses:
import { SettingsProvider, defaultSettings, ThemeProvider } from '@undefine-ui/design-system';
export function DesignSystemApp({ children }: { children: React.ReactNode }) {
return (
<SettingsProvider settings={defaultSettings}>
<ThemeProvider>{children}</ThemeProvider>
</SettingsProvider>
);
}Next.js App Router Setup
For Next.js 13+ with the App Router, install the additional MUI integration package and set up your providers:
pnpm add @mui/material-nextjs1. Create a providers file (app/providers.tsx):
'use client';
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
import { SettingsProvider, defaultSettings, ThemeProvider } from '@undefine-ui/design-system';
type ProvidersProps = {
children: React.ReactNode;
};
export function Providers({ children }: ProvidersProps) {
return (
<AppRouterCacheProvider options={{ key: 'css' }}>
<SettingsProvider settings={defaultSettings}>
<ThemeProvider>{children}</ThemeProvider>
</SettingsProvider>
</AppRouterCacheProvider>
);
}2. Update your root layout (app/layout.tsx):
import { getInitColorSchemeScript } from '@undefine-ui/design-system';
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<head>{getInitColorSchemeScript}</head>
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Key points:
AppRouterCacheProviderensures Emotion's CSS-in-JS works correctly with React Server Components and streaming SSRgetInitColorSchemeScriptprevents flash-of-unstyled-content (FOUC) during hydration by setting the color scheme before React loadssuppressHydrationWarningon<html>prevents React warnings from the color scheme scriptAdjust
@mui/material-nextjs/v15-appRouterto match your Next.js version (v13, v14, v15, etc.)SettingsProviderexposes the design-system preferences (mode, contrast, fonts, nav layout) through theuseSettingshook. Provide your ownsettingsobject if you want different defaults or if you persist user choices.ThemeProviderwraps MUI's CssVarsProvider with the Define theme (createTheme). It accepts any React children and automatically injectsCssBaselineand loads the required fonts (Work Sans and Geist).Both providers are exported from the package root so you can colocate them with your router/root layout.
Theming hooks
If you need to read or update theme settings at runtime:
import { useSettings } from '@undefine-ui/design-system';
const ThemeSwitcher = () => {
const { colorScheme, onUpdateField } = useSettings();
return (
<ToggleButtonGroup
value={colorScheme}
onChange={(_, value) => value && onUpdateField('colorScheme', value)}
>
{/* buttons */}
</ToggleButtonGroup>
);
};useSettings is a thin wrapper around React context, so it must be used inside SettingsProvider.
Component imports
import {
CopyButton,
CustomFormLabel,
Field,
Form,
Icon,
Logo,
LoadingScreen,
OTPInput,
Table,
Upload
} from '@undefine-ui/design-system';Storybook (pnpm dev:storybook) documents each component and token surface.
Icon library
The package includes a comprehensive icon library with 70+ SVG icons organized by category.
Available icons:
- Navigation: NavArrowLeft, NavArrowRight, NavArrowDown, NavArrowDownSolid, LongArrowUpLeftSolid, ArrowLeft, ArrowUpCircleSolid
- User: User, UserSolid, Building, Bank, BadgeCheck
- Form Controls: CheckboxDefault, CheckboxSelect, CheckboxIndeterminate, RadioDefault, RadioSelect
- Actions: Search, Copy, Edit, Trash, XMark, XMarkSolid, CloudUpload, Download, Settings, Plus, Minus, PlusSquare, Attachment, FilterList, RefreshDouble, MoreHorizontal, Repeat, ExpandLines
- Feedback: InfoCircle, InfoCircleSolid, CheckCircleSolid, HelpCircle, ClipboardCheck, Loader, BellNotification, Circle, ChatBubbleQuestionSolid
- Visibility: Eye, EyeClosed, ZoomIn, ZoomOut
- Date & Time: Calendar, Clock
- Data: SortUp, SortDown, StatUp, StatDown
- Toast: InfoToast, SuccessToast, WarningToast, ErrorToast
- Files: FilePdf, FileAudio, FileVideo, FileDocument, FileGeneric
- Location: MapPin, MapPinXMark
- Media: ModernTv, MegaPhone
- Commerce: SimpleCart
- Communication: SendMail
- System: KeyCommand
Usage:
import { Icon } from '@undefine-ui/design-system';
// Basic usage
<Icon icon="Search" sx={{ width: 24, height: 24 }} />
// With custom color
<Icon icon="InfoCircleSolid" sx={{ width: 32, height: 32, color: 'primary.main' }} />
// Different sizes
<Icon icon="Loader" sx={{ width: 16, height: 16 }} />
<Icon icon="Loader" sx={{ width: 48, height: 48 }} />Props:
icon- Icon name (required, type:IconType)sx- MUI sx prop for styling (size, color, etc.)- All
BoxPropsfrom MUI are supported
The Icon component renders SVG icons with full theme integration and accepts any MUI Box props for flexible styling.
DetailItem
A simple label-value display component for showing detail information. Commonly used in detail panels, drawers, and info sections to display structured data.
Usage:
import { DetailItem } from '@undefine-ui/design-system';
// Basic usage with value prop
<DetailItem label="Full Name" value="John Doe" />
// With custom children (for chips, badges, etc.)
<DetailItem label="Status">
<Chip label="Active" color="success" size="small" />
</DetailItem>
// Multiple items in a list
<Stack spacing={2}>
<DetailItem label="Booking ID" value="#001" />
<DetailItem label="Date Created" value="23 Aug 2025" />
<DetailItem label="Amount" value="N250,000" />
</Stack>Props:
label- The label text displayed above the value (required)value- The value text to display (optional if children provided)children- Custom React content to render instead of value text
Image
A high-performance lazy-loading image component with responsive image support, fallback handling, and smooth loading transitions. Perfect for optimizing image delivery across different devices and screen sizes.
Usage:
import { Image } from '@undefine-ui/design-system';
// Basic usage with lazy loading
<Image
src="/path/to/image.jpg"
alt="Description"
aspectRatio="16 / 9"
/>
// Responsive images with srcSet
<Image
src="/image-1280w.jpg"
srcSet="/image-320w.jpg 320w, /image-640w.jpg 640w, /image-1280w.jpg 1280w"
sizes="(max-width: 600px) 100vw, (max-width: 1200px) 50vw, 33vw"
alt="Responsive image"
aspectRatio="16 / 9"
/>
// Retina display support
<Image
src="/image.jpg"
srcSet="/image.jpg 1x, /[email protected] 2x, /[email protected] 3x"
alt="High DPI image"
/>
// With fallback for error handling
<Image
src="/primary-image.jpg"
fallbackSrc="/fallback-image.jpg"
alt="Image with fallback"
aspectRatio="4 / 3"
/>
// Disable lazy loading for above-the-fold images
<Image
src="/hero-image.jpg"
alt="Hero image"
lazy={false}
aspectRatio="21 / 9"
/>
// Custom loading indicator
<Image
src="/image.jpg"
alt="Custom loading"
loadingIndicator={<CircularProgress />}
/>
// With overlay content
<Image
src="/image.jpg"
alt="Image with overlay"
overlay={
<Box sx={{ p: 2, color: 'white' }}>
<Typography variant="h5">Overlay Text</Typography>
</Box>
}
/>
// Custom object-fit and position
<Image
src="/portrait.jpg"
alt="Portrait"
aspectRatio="1"
fit="contain"
position="top center"
/>Props:
src- Image source URL (required)alt- Alternative text for accessibility (required)lazy- Enable lazy loading with Intersection Observer (default:true)srcSet- Responsive image sources for different screen sizes/resolutionssizes- Defines which image size to use at different viewport widthsfallbackSrc- Fallback URL when the main source fails to loadaspectRatio- Aspect ratio to prevent layout shift (e.g.,"16 / 9",1.5)fit- Controlsobject-fitCSS property (default:"cover")position- Controlsobject-positionCSS property (default:"center")overlay- Optional overlay content rendered above the imagewithOverlay- Enables overlay even without contentloadingIndicator- Custom loading indicator componentrenderError- Custom error fallback UIobserverMargin- Intersection Observer root margin (default:"200px")imgSx- Additional styles for the image elementimgProps- Additional props for the underlying image elementonLoad- Callback fired when the image loadsonError- Callback fired when the image fails to load
Features:
- Lazy loading: Uses Intersection Observer for efficient viewport detection
- Responsive images: Full support for
srcSetandsizesattributes - Fallback handling: Automatic retry with fallback source on error
- Layout stability: Prevents layout shift with aspect ratio control
- Smooth transitions: Fade-in animation when image loads
- Custom loading states: Skeleton loader by default, customizable
- Error handling: Graceful error UI with customization options
- Overlay support: Render content on top of images
- Browser fallback: Uses native
loading="lazy"when IntersectionObserver unavailable - Flexible styling: Supports all MUI Box props for container and image
Performance Tips:
- Use
lazy={false}for above-the-fold images to avoid lazy loading delay - Always specify
aspectRatioto prevent layout shift during loading - Use
srcSetwith appropriate sizes for optimal image delivery - Set
observerMargin="400px"to preload images slightly before viewport
Toast Notifications
Toast notifications powered by Sonner with Define styling. Supports success, error, warning, info variants with optional descriptions and action buttons.
Setup:
Add the Toast component once at the root of your application:
import {
SettingsProvider,
defaultSettings,
ThemeProvider,
Toast
} from '@undefine-ui/design-system';
export function App({ children }: { children: React.ReactNode }) {
return (
<SettingsProvider settings={defaultSettings}>
<ThemeProvider>
<Toast />
{children}
</ThemeProvider>
</SettingsProvider>
);
}Usage:
import { toast } from '@undefine-ui/design-system';
// Basic toast
toast('This is a basic toast');
// Variants
toast.success('Success Notification');
toast.error('Error Notification');
toast.warning('Warning Notification');
toast.info('Information Notification');
toast.loading('Loading...');
// With description
toast.success('Event created', {
description: 'Your event has been created successfully'
});
// With action buttons
toast('Are you sure?', {
description: 'This action cannot be undone',
cancel: {
label: 'Cancel',
onClick: () => console.log('Cancelled')
},
action: {
label: 'Confirm',
onClick: () => console.log('Confirmed')
}
});
// Promise-based (auto shows loading → success/error)
toast.promise(fetchData(), {
loading: 'Saving changes...',
success: 'Changes saved successfully',
error: 'Failed to save changes'
});Features:
- Multiple variants: Success, error, warning, info, loading states
- Action buttons: Cancel and action buttons with callbacks
- Promise support: Auto-transition from loading to success/error
- Dark themed: Consistent dark background with themed icons
- Positioned: Top-right with expandable queue
File Upload (React Hook Form)
Form-ready file upload component integrated with React Hook Form. Supports single and multiple file uploads with automatic form state management, deduplication, and lifecycle callbacks.
Usage:
import { Form, Field } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';
const MyForm = () => {
const methods = useForm({
defaultValues: {
avatar: null,
documents: []
}
});
return (
<Form methods={methods} onSubmit={(data) => console.log(data)}>
{/* Single file upload */}
<Field.Upload name="avatar" helperText="Upload your profile picture" />
{/* Multiple files upload */}
<Field.Upload name="documents" multiple helperText="Upload multiple documents" />
{/* With lifecycle callbacks */}
<Field.Upload
name="photos"
multiple
onDropComplete={(files) => toast.success(`${files.length} files added`)}
onRemoveComplete={(file) => console.log('Removed:', file)}
onRemoveAllComplete={() => toast.info('All files cleared')}
/>
{/* Custom accept types */}
<Field.Upload
name="resume"
accept={{
'application/pdf': ['.pdf'],
'application/msword': ['.doc', '.docx']
}}
helperText="PDF or Word documents only"
/>
</Form>
);
};Props:
name- Form field name for react-hook-form (required)multiple- Enable multiple file selection (default:false)helperText- Helper text displayed below the upload areaaccept- Accepted file types object (e.g.,{ 'image/*': [] })maxSize- Maximum file size in bytesmaxFiles- Maximum number of files (for multiple mode)disabled- Disable the upload area
Lifecycle Callbacks:
These callbacks are called after the internal form state is updated, allowing you to add side effects like notifications, analytics, or logging without breaking form integration:
onDropComplete- Called after files are added to form state(files: File[]) => voidonDeleteComplete- Called after a file is deleted from form state (single mode)() => voidonRemoveComplete- Called after a file is removed from form state (multiple mode)(file: File | string) => voidonRemoveAllComplete- Called after all files are removed from form state (multiple mode)() => void
Features:
- Form integration: Automatically syncs with react-hook-form state
- Validation: Displays validation errors from react-hook-form
- Deduplication: Prevents adding duplicate files in multiple mode
- Safe callbacks: Lifecycle callbacks extend (not override) internal form logic
- Full control: Use the base
Uploadcomponent directly if you need to override behavior
Date Pickers (React Hook Form)
Form-ready date picker components integrated with React Hook Form. Includes RHFDatePicker, RHFTimePicker, and RHFDateTimePicker for seamless form state management.
Usage:
import { Form, RHFDatePicker, RHFTimePicker, RHFDateTimePicker } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';
const MyForm = () => {
const methods = useForm({
defaultValues: {
birthDate: null,
meetingTime: null,
eventStart: null
}
});
return (
<Form methods={methods} onSubmit={(data) => console.log(data)}>
{/* Date Picker */}
<RHFDatePicker name="birthDate" label="Birth Date" />
{/* Time Picker */}
<RHFTimePicker name="meetingTime" label="Meeting Time" />
{/* DateTime Picker */}
<RHFDateTimePicker name="eventStart" label="Event Start" />
{/* Clearable variant */}
<RHFDatePicker name="deadline" label="Deadline" clearable />
</Form>
);
};Props (shared by all variants):
name- Form field name for react-hook-form (required)label- Label for the picker inputhelperText- Helper text displayed below the inputclearable- Enable clear button (default:false)disabled- Disable the picker (default:false)- All MUI X DatePicker/TimePicker/DateTimePicker props are supported
Features:
- Form integration: Automatically syncs with react-hook-form state
- Validation: Displays validation errors from react-hook-form
- Clearable: Optional clear button to reset the value
- Theme-styled: Uses your theme's TextField styling
- Click-to-open: Opens picker on text field click
DateRangePicker
A date range picker with preset options (Today, Last 7 Days, etc.), dual calendars for start/end date selection, and custom date range support. Includes both a standalone DateRangePicker component and a DateRangeDropdown for toolbar integration.
Usage:
import {
DateRangePicker,
DateRangeDropdown,
defaultPresets,
getDateRangeFromPreset,
type DateRange,
type DatePreset
} from '@undefine-ui/design-system';
// Standalone DateRangePicker
const [range, setRange] = useState<DateRange>({ start: null, end: null });
const [preset, setPreset] = useState<DatePreset>('last30days');
<DateRangePicker
value={range}
preset={preset}
onChange={(newRange, newPreset) => {
setRange(newRange);
setPreset(newPreset);
}}
onCancel={() => console.log('Cancelled')}
/>
// DateRangeDropdown (for toolbars)
<DateRangeDropdown
value={range}
preset={preset}
onChange={(newRange, newPreset) => {
setRange(newRange);
setPreset(newPreset);
}}
/>
// With custom presets
const customPresets = [
{ value: 'today', label: 'Today' },
{ value: 'last7days', label: 'This Week' },
{ value: 'last30days', label: 'This Month' },
{ value: 'custom', label: 'Pick Dates' }
];
<DateRangePicker presets={customPresets} {...props} />
// Get date range programmatically
const last7DaysRange = getDateRangeFromPreset('last7days');
// { start: Date, end: Date }DateRangePicker Props:
value- Currently selected date range{ start: Date | null, end: Date | null }preset- Currently selected preset ('today','yesterday','last7days','last30days','last6months','lastyear','alltime','custom')onChange- Callback when date range changes(range, preset) => voidonCancel- Callback when cancel is clickedpresets- Custom preset options array (default:defaultPresets)showPresets- Show preset radio buttons (default:true)showActions- Show Cancel/Apply buttons (default:true)dateFormat- Date format for input display (default:'dd/MM/yyyy')minDate/maxDate- Date constraints
DateRangeDropdown Props:
- Inherits all
DateRangePickerprops buttonLabel- Fallback label for the dropdown button (default:'Date')disabled- Disable the dropdownpopoverSx- Custom styles for the popover- Button label automatically updates to show selected preset or custom date range (e.g., "Dec 1 - Dec 15, 2024")
Exported Helpers:
defaultPresets- Default preset options arraygetDateRangeFromPreset(preset)- Convert preset to actual date range
React Hook Form Integration (Field.DateRange):
import { Form, Field } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';
const MyForm = () => {
const methods = useForm({
defaultValues: {
dateRange: { start: null, end: null, preset: 'today' }
}
});
return (
<Form methods={methods} onSubmit={(data) => console.log(data)}>
<Field.DateRange name="dateRange" label="Select Date Range" />
</Form>
);
};Field.DateRange Props:
name- Form field name (required, stores{ start, end, preset })label- Label for the text fieldplaceholder- Placeholder text (default:'Select date range')helperText- Helper text below the inputdateFormat- Date format for display (default:'MMM d, yyyy')presets- Custom preset optionsdisabled- Disable the fieldsize- TextField size ('small'|'medium')fullWidth- Full width mode (default:true)
Toolbar Components
A collection of components for building data table toolbars with search, filters, sorting, view switching, and date range selection.
Usage:
import {
ToolbarButton,
ToolbarSearchField,
ToolbarViewSwitcher,
ToolbarFilterButton,
ToolbarSortButton,
ToolbarSettingsButton,
ToolbarTodayButton,
ToolbarDatePickerButton,
ToolbarSelectionButton,
FilterDropdown,
SortDropdown,
DateRangeDropdown,
type ViewOption,
type SortOption,
type SortDirection
} from '@undefine-ui/design-system';
// Base toolbar button with icon and label
<ToolbarButton icon="Filter" label="Filter" onClick={handleClick} />
// Base toolbar button with custom children (icon and label are ignored when children provided)
<ToolbarButton>
<CustomIcon />
<span>Custom Content</span>
</ToolbarButton>
// Specialized button components
<ToolbarFilterButton onClick={handleFilter} />
<ToolbarSortButton onClick={handleSort} />
<ToolbarSettingsButton onClick={handleSettings} />
<ToolbarTodayButton onClick={handleToday} />
<ToolbarDatePickerButton label="Last 30 days" onClick={handleDate} />
// Selection button - shows count with clear action
<ToolbarSelectionButton count={3} onClear={() => clearSelection()} />
<ToolbarSelectionButton count={5} label="items" onClear={handleClear} />
<ToolbarSelectionButton count={3} showClearButton={false} />
// Search field with clear button
<ToolbarSearchField
value={search}
onChange={(e) => setSearch(e.target.value)}
onClear={() => setSearch('')}
placeholder="Search..."
/>
// View switcher (year/month/week/day dropdown button)
const [view, setView] = useState<ViewOption>('week');
<ToolbarViewSwitcher value={view} onClick={() => setView('month')} />
// Filter dropdown - manages its own popover state, just pass children
<FilterDropdown>
<Form methods={methods}>
<Field.Text name="status" label="Status" size="small" select>
<MenuItem value="active">Active</MenuItem>
<MenuItem value="inactive">Inactive</MenuItem>
</Field.Text>
{/* Add more filter controls */}
</Form>
</FilterDropdown>
// Sort dropdown with unified onChange callback
const [sortValue, setSortValue] = useState('name');
const [sortDirection, setSortDirection] = useState<SortDirection>('asc');
const sortOptions: SortOption[] = [
{ value: 'name', label: 'Name' },
{ value: 'date', label: 'Date Created' }
];
<SortDropdown
options={sortOptions}
value={sortValue}
direction={sortDirection}
onChange={(value, direction) => {
setSortValue(value);
setSortDirection(direction);
}}
/>
// Complete toolbar example
<Stack direction="row" spacing={1} alignItems="center">
<ToolbarSearchField value={search} onChange={...} onClear={...} />
<Box sx={{ flexGrow: 1 }} />
<DateRangeDropdown value={range} preset={preset} onChange={...} />
<FilterDropdown>{filterContent}</FilterDropdown>
<SortDropdown options={sortOptions} value={sortValue} direction={sortDirection} onChange={...} />
<Divider orientation="vertical" flexItem />
<ToolbarViewSwitcher value={view} onClick={...} />
</Stack>ToolbarButton Props:
icon- Icon name from the icon library (used when children is not provided)label- Button label text (used when children is not provided)children- Custom content to render. When provided, icon and label props are ignoredopen- Whether the button is in open/active statedisabled- Disable the button- All standard MUI ButtonBase props
ToolbarSelectionButton Props:
count- Number of selected items (required)label- Text after the count (default:'selected')onClear- Callback when the clear button is clickedshowClearButton- Whether to show the clear button (default:true)- All standard MUI ButtonBase props (except
icon,open,children)
FilterDropdown Props:
children- Content to render inside the popover (use Form + Field components)buttonLabel- Custom label for the filter button (default:'Filter')onClose- Called when the popover closesdisabled- Disable the dropdownpopoverSx- Custom styles for the popover paperPopoverProps- Additional popover props
SortDropdown Props:
options- Array of sort options{ value: string, label: string }[]value- Currently selected sort fielddirection- Current sort direction ('asc'or'desc')onChange- Callback when sort changes(value: string, direction: SortDirection) => voidbuttonLabel- Custom label for the sort button (default:'Sort')ascLabel- Custom label for ascending (default:'Ascending')descLabel- Custom label for descending (default:'Descending')disabled- Disable the dropdown
ToolbarViewSwitcher Props:
value- Currently selected view ('year'|'month'|'week'|'day')open- Whether the button is in open/active state- All standard MUI ButtonBase props
Google Places Autocomplete
A Google Places address autocomplete component with structured address parsing, coordinates extraction, and React Hook Form integration. Perfect for address lookup, location selection, and form-based address capture.
Setup:
import { GooglePlacesProvider } from '@undefine-ui/design-system';
// Wrap your app with GooglePlacesProvider
function App() {
return (
<GooglePlacesProvider apiKey={process.env.GOOGLE_MAPS_API_KEY}>
{/* Your app content */}
</GooglePlacesProvider>
);
}Usage:
import {
GooglePlacesAutocomplete,
Field,
type PlaceType,
type PlaceDetails
} from '@undefine-ui/design-system';
// Basic usage
const [value, setValue] = useState<PlaceType | null>(null);
<GooglePlacesAutocomplete
label="Search for a location"
placeholder="Start typing..."
onChange={setValue}
/>
// With place details (includes parsed address and coordinates)
<GooglePlacesAutocomplete
label="Search address"
fetchPlaceDetails
onPlaceDetailsChange={(details) => {
console.log(details?.parsedAddress?.city); // "Lagos"
console.log(details?.parsedAddress?.country); // "Nigeria"
console.log(details?.coordinates?.latitude); // 6.5245
}}
/>
// With country restriction
<GooglePlacesAutocomplete
label="US addresses only"
componentRestrictions={{ country: 'us' }}
onChange={setValue}
/>React Hook Form Integration:
import { Form, Field } from '@undefine-ui/design-system';
import { useForm } from 'react-hook-form';
const MyForm = () => {
const methods = useForm({
defaultValues: {
location: null,
address: ''
}
});
return (
<Form methods={methods} onSubmit={(data) => console.log(data)}>
{/* Store full PlaceType object */}
<Field.GooglePlacesAutocomplete name="location" label="Location" valueType="full" />
{/* Store only description string */}
<Field.GooglePlacesAutocomplete name="address" label="Address" valueType="description" />
{/* Store PlaceDetails with parsed address */}
<Field.GooglePlacesAutocomplete
name="fullAddress"
label="Full Address"
valueType="details"
fetchPlaceDetails
/>
</Form>
);
};GooglePlacesAutocomplete Props:
label- Input labelplaceholder- Input placeholdervalue- Selected place (controlled)onChange- Callback when selection changes(place: PlaceType | null) => voidfetchPlaceDetails- Fetch full place details on selection (default:false)onPlaceDetailsChange- Callback with place details(details: PlaceDetails | null) => voidcomponentRestrictions- Country restrictions{ country: string | string[] }types- Place types to search for (e.g.,['address'],['establishment'])debounceMs- Debounce delay in milliseconds (default:300)error/errorMessage/helperText- Error and helper text display- All standard MUI TextField props
Field.GooglePlacesAutocomplete Props:
name- Form field name (required)valueType- What to store in form:'full'(PlaceType),'description'(string), or'details'(PlaceDetails)onValueChange- Callback when value changes (raw PlaceType)onPlaceDetailsChange- Callback when place details are fetched- All
GooglePlacesAutocompleteprops exceptvalue,onChange,error,errorMessage
PlaceDetails Structure:
interface PlaceDetails {
placeId: string;
description: string;
formattedAddress?: string;
lat?: number;
lng?: number;
// Structured parsed address
parsedAddress?: {
streetNumber?: string;
street?: string;
address?: string; // Full formatted address
neighborhood?: string;
city?: string;
state?: string;
stateCode?: string;
country?: string;
countryCode?: string;
postalCode?: string;
county?: string;
};
// Coordinates
coordinates?: {
latitude: number;
longitude: number;
};
}Exported Utilities:
GooglePlacesProvider- Context provider for API keyuseGooglePlacesAutocomplete- Hook for custom implementationsparseAddressComponents- Parse raw Google address components into structured format
OTP Input
A one-time password input component with keyboard navigation, paste support, and validation. Perfect for email/SMS verification, PIN codes, and security codes.
Usage:
import { OTPInput, Field } from '@undefine-ui/design-system';
// Basic usage
<OTPInput
length={6}
onChange={(otp) => console.log(otp)}
onComplete={(otp) => console.log('Complete:', otp)}
helperText="Enter the 6-digit code"
/>
// With React Hook Form
<Field.OTP
name="verificationCode"
length={6}
helperText="Enter the code sent to your email"
/>
// 4-digit PIN
<Field.OTP
name="pin"
length={4}
helperText="Enter your PIN"
/>
// Error state
<OTPInput
length={6}
error
helperText="Invalid code. Please try again."
/>Props:
length- Number of OTP input fields (default:6)onChange- Callback fired when OTP value changes(otp: string) => voidonComplete- Callback fired when all fields are filled(otp: string) => voiderror- Show error state (default:false)helperText- Helper text displayed below the inputcontainerProps- Props passed to the container Box component
Features:
- Keyboard navigation: Arrow keys to move between fields, Backspace to clear
- Paste support: Automatically fills all fields from clipboard
- Auto-focus: Moves to next field after entering a digit
- Validation: Only accepts numeric input
- Error handling: Visual error states with helper text
- Responsive: Adapts input size on mobile devices
Hook Form Integration:
The Field.OTP component automatically integrates with React Hook Form, providing validation and error handling out of the box.
FormLabel
A custom form label component with support for optional text, tooltips, and custom icons. Perfect for creating consistent form field labels across your application.
Usage:
import { CustomFormLabel } from '@undefine-ui/design-system';
// Basic label
<CustomFormLabel label="Email Address" />
// Optional field label
<CustomFormLabel label="Phone Number" optional />
// With tooltip
<CustomFormLabel
label="API Key"
tooltip="Your API key can be found in the developer settings."
/>
// Optional with tooltip
<CustomFormLabel
label="Company Name"
optional
tooltip="Enter your company name if applicable."
/>
// With custom icon
<CustomFormLabel
label="Password"
tooltip="Password must be at least 8 characters long."
icon={<Icon icon="HelpCircle" sx={{ width: 16, height: 16, color: 'primary.main' }} />}
/>
// Complete form example
<Box>
<CustomFormLabel label="Full Name" />
<TextField fullWidth placeholder="Enter your full name" size="small" />
</Box>Props:
label- Label text to display (required)optional- Show "(Optional)" text after label (default:false)tooltip- Tooltip text shown on hover of info iconicon- Custom icon for tooltip (default:InfoCircleicon)sx- MUI sx prop for styling- All MUI
FormLabelPropsare supported (exceptchildren)
Features:
- Optional indicator: Shows "(Optional)" text for non-required fields
- Tooltip support: Info icon with tooltip for additional context
- Custom icons: Replace default info icon with custom icons
- Flexible styling: Supports all MUI FormLabel props and sx styling
- Theme integration: Uses theme variables for consistent styling
Dialog
Custom dialog components with close button and feedback variations. Includes a base CustomDialog with a close button positioned on the right, and a FeedbackDialog for success, error, and confirmation states.
Usage:
import { CustomDialog, FeedbackDialog } from '@undefine-ui/design-system';
// Basic CustomDialog with close button
<CustomDialog open={open} onClose={handleClose}>
<Typography variant="h6">Custom Dialog Title</Typography>
<Typography variant="body2">
This is a custom dialog with a close button on the right.
</Typography>
</CustomDialog>
// Success feedback dialog
<FeedbackDialog
open={open}
onClose={handleClose}
image="/path/to/success-image.png"
title="Board added successfully"
description="Your new board has been created."
actions={
<>
<Button variant="contained" fullWidth onClick={handleGoToBoard}>
Go to board
</Button>
<Button variant="text" color="inherit" fullWidth onClick={handleClose}>
Close
</Button>
</>
}
/>
// Confirmation dialog with horizontal actions
<FeedbackDialog
open={open}
onClose={handleClose}
title="Confirm deletion"
description="Are you sure you want to delete this item?"
actions={
<Stack direction="row" spacing={1.5} sx={{ width: '100%' }}>
<Button variant="outlined" fullWidth onClick={handleClose}>
Cancel
</Button>
<Button variant="contained" color="error" fullWidth onClick={handleDelete}>
Delete
</Button>
</Stack>
}
feedbackSlotProps={{
actions: { flexDirection: 'row' }
}}
/>
// Custom styling
<FeedbackDialog
open={open}
onClose={handleClose}
title="Welcome aboard! 🎉"
description="Your account has been created successfully."
actions={
<Button variant="contained" fullWidth>Get Started</Button>
}
feedbackSlotProps={{
title: { fontSize: '1.5rem', color: 'primary.main' },
description: { maxWidth: 280 }
}}
/>CustomDialog Props:
open- Controls dialog visibility (required)onClose- Callback when close button is clicked- All MUI
DialogPropsare supported
FeedbackDialog Props:
open- Controls dialog visibility (required)onClose- Callback when close button is clickedimage- URL for the feedback imagetitle- Title text to displaydescription- Description text below the titleactions- Action elements (buttons, etc.)feedbackSlotProps- Custom styles forimage,title,description, andactionsslots
Features:
- Close button: Positioned on the top-right for easy dismissal
- Flexible content: CustomDialog accepts any children content
- Feedback states: FeedbackDialog supports success, error, and confirmation patterns
- Customizable: Style individual elements via feedbackSlotProps
- Theme integration: Uses theme variables for consistent styling
Lightbox
A full-featured image gallery lightbox component with zoom, pan, navigation, thumbnails, and keyboard support. Perfect for image galleries, product showcases, and media viewers.
Usage:
import { Lightbox, useLightbox, type LightboxSlide } from '@undefine-ui/design-system';
// Define your slides
const slides: LightboxSlide[] = [
{
src: '/images/photo1.jpg',
thumbnail: '/images/photo1-thumb.jpg',
alt: 'Photo 1',
title: 'Mountain Landscape',
description: 'A beautiful mountain view at sunset'
},
{
src: '/images/photo2.jpg',
thumbnail: '/images/photo2-thumb.jpg',
alt: 'Photo 2'
}
];
// Using the useLightbox hook (recommended)
const MyGallery = () => {
const lightbox = useLightbox(slides);
return (
<>
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 100px)', gap: 1 }}>
{slides.map((slide, index) => (
<Box
key={index}
component="img"
src={slide.thumbnail}
alt={slide.alt}
onClick={() => lightbox.onOpen(index)}
sx={{ cursor: 'pointer', width: 100, height: 75, objectFit: 'cover' }}
/>
))}
</Box>
<Lightbox
open={lightbox.open}
onClose={lightbox.onClose}
slides={lightbox.slides}
index={lightbox.selected}
onIndexChange={lightbox.onSetSelected}
/>
</>
);
};
// Controlled usage
const [open, setOpen] = useState(false);
const [index, setIndex] = useState(0);
<Lightbox
open={open}
onClose={() => setOpen(false)}
slides={slides}
index={index}
onIndexChange={setIndex}
/>
// Without thumbnails
<Lightbox
open={open}
onClose={handleClose}
slides={slides}
showThumbnails={false}
/>
// Without zoom functionality
<Lightbox
open={open}
onClose={handleClose}
slides={slides}
enableZoom={false}
/>
// Minimal UI (no thumbnails, no counter, no zoom)
<Lightbox
open={open}
onClose={handleClose}
slides={slides}
showThumbnails={false}
showCounter={false}
enableZoom={false}
/>
// Custom zoom levels
<Lightbox
open={open}
onClose={handleClose}
slides={slides}
zoomLevels={[0.5, 1, 6]} // [min, default, max]
/>
// Disable looping
<Lightbox
open={open}
onClose={handleClose}
slides={slides}
loop={false}
/>Lightbox Props:
open- Controls lightbox visibility (required)onClose- Callback when lightbox is closed (required)slides- Array of slide objectsLightboxSlide[](required)index- Current slide index (default:0)onIndexChange- Callback when index changes(index: number) => voidshowThumbnails- Show thumbnail strip at bottom (default:true)showCounter- Show slide counter in toolbar (default:true)enableZoom- Enable zoom functionality (default:true)loop- Enable infinite navigation (default:true)zoomLevels- Zoom levels as[min, default, max](default:[1, 1, 4])
LightboxSlide Interface:
interface LightboxSlide {
src: string; // Full-size image URL (required)
thumbnail?: string; // Thumbnail URL (falls back to src)
alt?: string; // Alt text for accessibility
title?: string; // Title displayed below image
description?: string; // Description displayed below title
}useLightbox Hook:
The useLightbox hook provides a convenient way to manage lightbox state:
const lightbox = useLightbox(slides);
// Returns:
{
open: boolean; // Whether lightbox is open
selected: number; // Current slide index
slides: LightboxSlide[]; // Slides array
onOpen: (index?: number) => void; // Open at specific index
onClose: () => void; // Close lightbox
onSetSelected: (index: number) => void; // Change slide
setSlides: (slides: LightboxSlide[]) => void; // Update slides
}Keyboard Navigation:
←/→- Navigate between slidesEscape- Close lightbox
Mouse/Touch Controls:
- Scroll wheel - Zoom in/out when zoom is enabled
- Double-click - Toggle between default and max zoom
- Drag - Pan around when zoomed in
- Click thumbnails - Jump to specific slide
- Click arrows - Navigate to previous/next slide
Features:
- Zoom & Pan: Mouse wheel zoom and drag-to-pan when zoomed
- Keyboard navigation: Arrow keys and Escape support
- Thumbnail strip: Quick navigation with visual thumbnails
- Slide counter: Shows current position (e.g., "2 / 10")
- Captions: Optional title and description per slide
- Loop navigation: Seamlessly cycle through images
- Loading states: Skeleton placeholders while images load
- Responsive: Full-screen overlay with proper mobile handling
- Accessible: Proper ARIA labels and keyboard support
Drawer
A drawer component with a fixed header containing a title and close button. The content area is scrollable while the header remains fixed. Built on top of MUI Drawer with enhanced styling and layout.
Usage:
import { CustomDrawer } from '@undefine-ui/design-system';
// Basic drawer
<CustomDrawer open={open} onClose={handleClose} title="Drawer Title">
<Typography variant="body2">
This is the drawer content. It scrolls independently while the header stays fixed.
</Typography>
</CustomDrawer>
// Drawer with details layout
<CustomDrawer open={open} onClose={handleClose} title="Booking details">
<Stack spacing={3}>
<Box>
<Typography variant="body2" color="text.body">Status</Typography>
<Chip label="Confirm" color="success" size="small" />
</Box>
<Box>
<Typography variant="body2" color="text.body">Booking ID</Typography>
<Typography variant="subtitle2">#001</Typography>
</Box>
<Box>
<Typography variant="body2" color="text.body">Amount</Typography>
<Typography variant="subtitle2">N250,000</Typography>
</Box>
</Stack>
</CustomDrawer>
// Left-anchored drawer
<CustomDrawer
open={open}
onClose={handleClose}
title="Left Drawer"
anchor="left"
>
<Typography variant="body2">
This drawer opens from the left side.
</Typography>
</CustomDrawer>
// Drawer with form
<CustomDrawer open={open} onClose={handleClose} title="Time slot">
<Stack spacing={3}>
<TextField label="Date" size="small" fullWidth />
<TextField label="Time" size="small" fullWidth />
<Button variant="contained" fullWidth>Save</Button>
</Stack>
</CustomDrawer>Props:
open- Controls drawer visibility (required)title- Title text displayed in the fixed headeronClose- Callback when close button is clickedanchor- Side from which the drawer appears (default:'right')slotProps- Custom props for MUI Drawer slots (paper, etc.)- All MUI
DrawerPropsare supported
Features:
- Fixed header: Title and close button stay visible while scrolling
- Scrollable content: Content area scrolls independently
- Responsive width: Full width on mobile, 400px on larger screens
- Multiple anchors: Supports left, right, top, and bottom positioning
- Close button: XMark icon button for easy dismissal
- Theme integration: Uses theme variables for consistent styling
Empty Content
A flexible empty state component for displaying placeholder content when data is unavailable. Perfect for empty lists, search results, or any state where content hasn't been loaded yet.
Usage:
import { EmptyContent } from '@undefine-ui/design-system';
// Basic usage
<EmptyContent title="No data" />
// With description
<EmptyContent
title="No results found"
description="Try adjusting your search or filter to find what you're looking for."
/>
// With image
<EmptyContent
title="No notifications"
description="You're all caught up!"
imgUrl="/assets/icons/empty/ic-notification.svg"
/>
// Filled variant (with background)
<EmptyContent
filled
title="No items in cart"
description="Add items to your cart to see them here."
/>
// With action button
<EmptyContent
title="No projects yet"
description="Create your first project to get started."
action={
<Button variant="contained">Create Project</Button>
}
/>
// Custom styling with slotProps
<EmptyContent
title="Custom Styled"
description="Customize title and description styles."
slotProps={{
title: { color: 'primary.main', fontWeight: 700 },
description: { color: 'text.secondary', maxWidth: 300 }
}}
/>Props:
title- Title text to display (default:'No data')description- Description text below the titleimgUrl- URL for an optional image to displayfilled- Show filled background variant with border (default:false)action- Optional action element (button, link, etc.)slotProps- Custom styles forimg,title, anddescriptionslotssx- MUI sx prop for container styling- All
StackPropsfrom MUI are supported
Features:
- Flexible layout: Centers content vertically and horizontally
- Filled variant: Adds background color and dashed border
- Image support: Display custom illustrations or icons
- Action slot: Add buttons or links for user interaction
- Customizable: Style individual elements via slotProps
- Theme integration: Uses theme variables for consistent styling
Logo assets
The package exports two Logo components:
<Logo />
Renders logo images hosted on Cloudinary by default. The component automatically selects the appropriate variant based on props:
| Variant flag combo | Asset served |
| -------------------------- | ----------------------------------- |
| isFull={false} (default) | Single logo (60px width) |
| isFull | Full logo with text (120px width) |
| isWhite | White variant for dark backgrounds |
| isBlack | Black variant for light backgrounds |
| isWhite + isFull | White full logo |
| isBlack + isFull | Black full logo |
All logo assets are served from Cloudinary and don't require any local files in your host app.
<Logo isFull isWhite href="/dashboard" />Props:
isFull- Use full logo with text (default:false)isWhite- Use white variant (default:false)isBlack- Use black variant (default:false)disableLink- Render without link wrapper (default:false)href- Link destination (default:'/')LinkComponent- Custom link component (default:'a')src- Override logo source URLalt- Image alt text (default:'Undefine UI logo')sx- MUI sx prop for styling
<AnimatedLogo />
An animated SVG version of the logo perfect for splash screens and loading states. Features a smooth infinite animation sequence with staggered timing for visual interest.
import { AnimatedLogo } from '@undefine-ui/design-system';
<AnimatedLogo />;The animated logo automatically plays on mount with:
- Background fade-in
- Left bars sliding in from the left
- D letter scaling in with a bounce effect
- Continuous infinite loop
Ideal for splash screens, loading overlays, or brand storytelling moments.
ApexCharts Styling
The design system provides styling utilities for ApexCharts integration, ensuring charts match the Define theme.
Installation:
pnpm add apexcharts react-apexchartsUsage:
import ReactApexChart from 'react-apexcharts';
import { useTheme, styled } from '@mui/material/styles';
import { apexChartsStyles, getDefaultChartOptions } from '@undefine-ui/design-system';
// Create a styled wrapper
const ChartWrapper = styled('div')(({ theme }) => ({
...apexChartsStyles(theme)
}));
function LineChart() {
const theme = useTheme();
const chartOptions = {
...getDefaultChartOptions(theme),
xaxis: {
...getDefaultChartOptions(theme).xaxis,
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul']
}
};
const chartSeries = [
{ name: 'Bookings', data: [10, 41, 35, 51, 49, 62, 69] },
{ name: 'Revenue', data: [20, 35, 50, 30, 45, 55, 40] }
];
return (
<ChartWrapper>
<ReactApexChart type="line" series={chartSeries} options={chartOptions} height={350} />
</ChartWrapper>
);
}Available exports:
apexChartsStyles(theme)- CSS styles to apply to a wrapper element (tooltips, legends, labels)getDefaultChartOptions(theme)- Default ApexCharts options with theme colors, typography, and grid styling
Export surface
Everything is re-exported from src/index.ts. Key groups:
| Group | Exports |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Components | CopyButton, CustomDialog, CustomDrawer, EmptyContent, FeedbackDialog, HookForm helpers (Form, Field, RHFSwitch, etc.), Icon, Image, Lightbox, Logo, LoadingScreen, OTPInput, Table, Upload |
| Hooks | useBoolean, useCopyToClipboard, useEventListener, useLightbox, useLocalStorage, useResponsive, useSettings, useSetState, useScrollOffsetTop, usePopover, useCountdown |
| Contexts | SettingsProvider, SettingsContext, defaultSettings, SettingsValueProps |
| Theme | ThemeProvider, createTheme, colorSchemes, components, palette, radius, shadows, customSpacing, utilities such as varAlpha, bgGradient, hideScrollX/Y |
| Utilities | changeCase helpers, formatNumber, fullname-utils, generic helpers |
You can also import the theme pieces directly to compose your own MUI theme or to extend tokens in Storybook.
Customising the theme
- Call
createTheme(settings)to get the configuredThemeobject (useful for tests or SSR). - Theme tokens live under
src/theme/core. If you need to override palette/typography/etc., spread the exports into your ownextendThemecall. updateCoreWithSettingsandupdateComponentsWithSettingsare exported for advanced scenarios (e.g., you want to override the default settings object before rendering).
Scripts
pnpm build– bundle ESM/CJS output +.d.tsintodist/pnpm storybook– run Storybook locallypnpm build-storybook– static Storybook outputpnpm test– run Vitest (once specs are added)
