Skip to main content

Components

Complete reference for all @elcto/ui components.

ApiHealthBanner

Display API health status with automatic health checks and exponential backoff.

import {
ApiHealthBanner,
useApiHealth,
useShowApiHealthBanner,
} from "@elcto/ui/components";

function App() {
const { state: apiHealth, refresh: refreshApiHealth } = useApiHealth();
const showBanner = useShowApiHealthBanner(apiHealth);

const labels = {
unreachable: "Unable to connect to the API server.",
serviceIssues: "Service issues:",
database: "Database",
redis: "Redis",
websocket: "WebSocket",
checking: "Checking...",
refreshNow: "Click to refresh now",
};

return (
<div>
<ApiHealthBanner
apiHealth={apiHealth}
show={showBanner}
labels={labels}
fixedTop="4rem"
sidebarOffset="16rem"
onRefresh={refreshApiHealth}
/>
{/* Your app content */}
</div>
);
}

ApiHealthBanner Props:

PropTypeDefaultDescription
apiHealthApiHealthStaterequiredHealth state from useApiHealth
showbooleanrequiredWhether to show the banner
labelsApiHealthBannerLabelsrequiredTranslated label strings
fixedTopstring'0'Fixed position top offset
sidebarOffsetstring-Sidebar offset for positioning
onRefresh() => void-Manual refresh callback

useApiHealth Options:

const { state, refresh } = useApiHealth({
baseInterval: 30000, // 30 seconds
maxInterval: 600000, // 10 minutes (max with backoff)
apiUrl: process.env.NEXT_PUBLIC_API_URL,
});
OptionTypeDefaultDescription
baseIntervalnumber30000Base check interval (ms)
maxIntervalnumber600000Max interval with backoff (ms)
apiUrlstringNEXT_PUBLIC_API_URLAPI URL to check

ApiHealthState:

interface ApiHealthState {
status: "healthy" | "unhealthy" | "unreachable";
services: {
database: { status: string };
redis: { status: string };
websocket: { status: string };
};
nextCheckIn: number | null; // seconds until next check
isChecking: boolean;
}

ApiHealthBannerLabels:

interface ApiHealthBannerLabels {
unreachable: string;
serviceIssues: string;
database: string;
redis: string;
websocket: string;
checking: string;
refreshNow: string;
}

AuditIcon

Renders appropriate icons for audit events based on event type.

import {
AuditIcon,
AuditIconElements,
getAuditIconComponent,
getIconByName,
renderAuditIcon,
renderIconByName,
} from "@elcto/ui/components";

// Component usage
<AuditIcon eventType="login" className="w-4 h-4" />
<AuditIcon eventType="2fa_enabled" className="w-5 h-5 text-amber-500" />

// Pre-built size variants
{AuditIconElements.sm("login")} // w-3 h-3
{AuditIconElements.md("login")} // w-4 h-4
{AuditIconElements.lg("login")} // w-5 h-5

// Function usage
const Icon = getAuditIconComponent("login");
<Icon className="w-4 h-4" />

// Render helper
{renderAuditIcon("2fa_enabled", "w-5 h-5")}

AuditIcon Props:

PropTypeDefaultDescription
eventTypestringrequiredAudit event type (e.g., "login", "2fa_enabled")
classNamestring"w-4 h-4"CSS classes for sizing and color

Exported Functions:

FunctionDescription
getAuditIconComponent(eventType)Returns the Lucide icon component for an event type
getIconByName(iconName)Returns icon component by icon name
renderAuditIcon(eventType, className)Renders icon as React element
renderIconByName(iconName, className)Renders icon by name as React element

AuditIconElements:

KeySize
smw-3 h-3
mdw-4 h-4
lgw-5 h-5

Alert

Display status messages with semantic styling.

import { Alert } from "@elcto/ui/components";

<Alert variant="success" title="Success!">
Your changes have been saved.
</Alert>

<Alert variant="error" title="Error" onClose={() => setShow(false)}>
Something went wrong. Please try again.
</Alert>

<Alert variant="warning">
This action cannot be undone.
</Alert>

<Alert variant="info">
Your session will expire in 5 minutes.
</Alert>

Props:

PropTypeDefaultDescription
variant'error' | 'warning' | 'success' | 'info'requiredAlert style
childrenReactNoderequiredAlert content
titlestring-Optional title
onClose() => void-Close callback (shows X button)
classNamestring-Additional CSS classes

AppSwitcher

Navigation dropdown for switching between apps.

import { AppSwitcher, LayoutGridIcon } from "@elcto/ui/components";
import { Settings, User, FileText, Shield } from "lucide-react";

const apps = [
{
key: "backend",
label: "Backend",
description: "Admin console",
icon: <Settings className="w-5 h-5" />,
url: "https://console.elcto.com",
current: true,
},
{
key: "id",
label: "ID",
description: "Account settings",
icon: <User className="w-5 h-5" />,
url: "https://id.elcto.com",
},
{
key: "docs",
label: "Docs",
icon: <FileText className="w-5 h-5" />,
url: "https://docs.elcto.com",
},
{
key: "policies",
label: "Policies",
icon: <Shield className="w-5 h-5" />,
url: "https://policies.elcto.com",
},
];

<AppSwitcher apps={apps} headerLabel="Apps" columns={2} />

Props:

PropTypeDefaultDescription
appsAppItem[]requiredList of apps to display
headerLabelstring'Apps'Header text
columns2 | 3 | 43Grid columns
classNamestring-Additional CSS classes

AppItem:

PropTypeDescription
keystringUnique identifier
labelstringDisplay name
descriptionstringOptional description
iconReactNodeIcon element
urlstringNavigation URL
currentbooleanWhether this is the current app

Avatar

User avatar with image fallback to initials.

import { Avatar } from "@elcto/ui/components";

<Avatar src="/user.jpg" name="John Doe" size={40} />
<Avatar name="Jane Smith" size={32} />
<Avatar size={48} /> // Shows default icon

Props:

PropTypeDefaultDescription
srcstring | null-Image URL
namestring | null-User name (for initials)
sizenumber40Size in pixels
classNamestring-Additional CSS classes

Badge

Status badges and labels.

import { Badge } from "@elcto/ui/components";

<Badge variant="success">Active</Badge>
<Badge variant="warning">Pending</Badge>
<Badge variant="error">Failed</Badge>
<Badge variant="info">New</Badge>
<Badge variant="neutral">Draft</Badge>

Props:

PropTypeDefaultDescription
variant'success' | 'warning' | 'error' | 'info' | 'neutral''neutral'Badge style
childrenReactNoderequiredBadge content
classNamestring-Additional CSS classes

Full-width notification banners.

import { Banner } from "@elcto/ui/components";

<Banner variant="warning" onClose={() => setShow(false)}>
System maintenance scheduled for tonight.
</Banner>

<Banner variant="error">
Connection lost. Reconnecting...
</Banner>

Props:

PropTypeDefaultDescription
variant'info' | 'warning' | 'error' | 'success''info'Banner style
childrenReactNoderequiredBanner content
onClose() => void-Close callback
classNamestring-Additional CSS classes

BlurredText

Privacy-aware text display with blur effect.

import { BlurredText } from "@elcto/ui/components";

// Controlled component
<BlurredText
text="secret-api-key-12345"
isBlurred={isPrivacyMode}
onToggle={() => setIsPrivacyMode(!isPrivacyMode)}
blurIntensity="md"
/>

// Without toggle button
<BlurredText
text="hidden-value"
isBlurred={true}
showToggle={false}
/>

Props:

PropTypeDefaultDescription
textstringrequiredText to display
isBlurredbooleanrequiredWhether text is blurred
onToggle() => void-Toggle callback
showTogglebooleantrueShow toggle button
blurIntensity'xs' | 'sm' | 'md' | 'lg''md'Blur intensity
classNamestring-Additional CSS classes

Button

Interactive buttons with variants and states.

import { Button } from "@elcto/ui/components";
import { Plus, ArrowRight } from "lucide-react";

// Variants
<Button variant="brand">Primary Action</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="danger">Delete</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="outline">Outline</Button>

// Sizes
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

// With icons
<Button leftIcon={<Plus className="w-4 h-4" />}>Add Item</Button>
<Button rightIcon={<ArrowRight className="w-4 h-4" />}>Next</Button>

// Loading state
<Button isLoading>Saving...</Button>

// Disabled
<Button disabled>Disabled</Button>

Props:

PropTypeDefaultDescription
variant'brand' | 'secondary' | 'danger' | 'ghost' | 'outline''brand'Button style
size'sm' | 'md' | 'lg''md'Button size
isLoadingbooleanfalseLoading state
leftIconReactNode-Icon before text
rightIconReactNode-Icon after text
disabledbooleanfalseDisabled state

Also accepts all standard <button> HTML attributes.


Card

Content container components.

import {
Card,
CardHeader,
CardContent,
CardFooter,
ActionCard,
StatusCard,
} from "@elcto/ui/components";

// Basic card
<Card>
<CardHeader>
<h3>Card Title</h3>
</CardHeader>
<CardContent>
Card content goes here.
</CardContent>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>

// Action card
<ActionCard
title="Quick Action"
description="Click to perform action"
onClick={() => handleAction()}
/>

// Status card
<StatusCard
variant="success"
title="Connected"
description="All services operational"
/>

Card Props:

PropTypeDefaultDescription
childrenReactNoderequiredCard content
classNamestring-Additional CSS classes
variant'default' | 'flat''default'Card style

ActionCard Props:

PropTypeDefaultDescription
titlestringrequiredCard title
descriptionstring-Card description
iconReactNode-Icon element
onClick() => void-Click handler

StatusCard Props:

PropTypeDefaultDescription
variant'success' | 'warning' | 'error' | 'info''info'Status style
titlestringrequiredCard title
descriptionstring-Card description

Checkbox

Styled checkbox input with label and description support.

import { Checkbox } from "@elcto/ui/components";

// Basic checkbox
<Checkbox
label="Accept terms and conditions"
checked={accepted}
onChange={(e) => setAccepted(e.target.checked)}
/>

// With description
<Checkbox
label="Email notifications"
description="Receive email updates about your account"
checked={emailEnabled}
onChange={(e) => setEmailEnabled(e.target.checked)}
/>

// Size variants
<Checkbox label="Small" size="sm" />
<Checkbox label="Medium" size="md" />
<Checkbox label="Large" size="lg" />

// Error state
<Checkbox
label="Required field"
error
errorMessage="You must accept the terms"
/>

// Disabled
<Checkbox label="Disabled option" disabled />

Props:

PropTypeDefaultDescription
labelReactNode-Label text
descriptionstring-Description text below label
size'sm' | 'md' | 'lg''md'Size variant
errorbooleanfalseError state
errorMessagestring-Error message (also sets error state)
wrapperClassNamestring-Class for the wrapper div

Also accepts all standard <input type="checkbox"> HTML attributes.


CodeInput

OTP/verification code input with auto-focus.

import { CodeInput } from "@elcto/ui/components";

<CodeInput
length={6}
value={code}
onChange={setCode}
onComplete={(code) => verifyCode(code)}
/>

Props:

PropTypeDefaultDescription
lengthnumber6Number of digits
valuestring-Controlled value
onChange(value: string) => void-Change handler
onComplete(code: string) => void-Called when all digits entered
errorbooleanfalseError state
disabledbooleanfalseDisabled state

Accessible dropdown menu with animations.

import {
Dropdown,
DropdownTrigger,
DropdownMenu,
DropdownItem,
DropdownDivider,
DropdownHeader,
DropdownLabel,
} from "@elcto/ui/components";
import { Settings, LogOut, User } from "lucide-react";

<Dropdown>
<DropdownTrigger>
Open Menu
</DropdownTrigger>

<DropdownMenu align="right" width={200}>
<DropdownHeader>
<p className="font-medium">John Doe</p>
<p className="text-sm text-muted">john@example.com</p>
</DropdownHeader>

<DropdownDivider />

<DropdownLabel>Account</DropdownLabel>

<DropdownItem icon={<User className="w-4 h-4" />} as="a" href="/profile">
Profile
</DropdownItem>

<DropdownItem icon={<Settings className="w-4 h-4" />}>
Settings
</DropdownItem>

<DropdownDivider />

<DropdownItem icon={<LogOut className="w-4 h-4" />} variant="danger">
Sign Out
</DropdownItem>
</DropdownMenu>
</Dropdown>

Dropdown Props:

PropTypeDefaultDescription
childrenReactNoderequiredTrigger and menu
openboolean-Controlled open state
onOpenChange(open: boolean) => void-Open state callback

DropdownTrigger Props:

PropTypeDefaultDescription
showChevronbooleantrueShow chevron icon

Also accepts all standard <button> HTML attributes.

DropdownMenu Props:

PropTypeDefaultDescription
align'left' | 'right''right'Menu alignment
widthnumber | string224Menu width

DropdownItem Props:

PropTypeDefaultDescription
iconReactNode-Item icon
as'button' | 'a''button'Element type
hrefstring-Link href (when as="a")
variant'default' | 'danger''default'Item style
closeOnClickbooleantrueClose menu on click

DateTimePicker

Date, time, or datetime picker with calendar dropdown.

import { DateTimePicker } from "@elcto/ui/components";

// Date only
<DateTimePicker
mode="date"
value={date}
onChange={setDate}
placeholder="Select date..."
/>

// Date and time
<DateTimePicker
mode="datetime"
value={datetime}
onChange={setDatetime}
placeholder="Select date and time..."
/>

// Time only
<DateTimePicker
mode="time"
value={time}
onChange={setTime}
placeholder="Select time..."
/>

// With min/max constraints
<DateTimePicker
mode="date"
value={date}
onChange={setDate}
min={new Date("2024-01-01")}
max={new Date("2024-12-31")}
/>

// Custom labels for i18n
<DateTimePicker
mode="datetime"
value={date}
onChange={setDate}
labels={{
days: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
months: ["Januar", "Februar", "März", ...],
hour: "Stunde",
minute: "Minute",
apply: "Anwenden",
selectDate: "Datum wählen...",
}}
/>

Props:

PropTypeDefaultDescription
valuestring | Date | null-Current value (ISO 8601 string or Date)
onChange(value: string | null) => void-Change callback
mode'date' | 'datetime' | 'time''date'Picker mode
placeholderstring-Placeholder text
size'sm' | 'md' | 'lg''md'Size variant
errorbooleanfalseError state
disabledbooleanfalseDisabled state
minstring | Date-Minimum date
maxstring | Date-Maximum date
clearablebooleantrueShow clear button
localestringBrowser localeLocale for formatting
namestring-Name for form submission
leftIconReactNode-Custom left icon
dropdownAlign'left' | 'right''left'Dropdown alignment
labelsDateTimePickerLabels-Labels for translations

DateTimePickerLabels:

KeyTypeDescription
days[string x 7]Day abbreviations (Su, Mo, Tu...)
months[string x 12]Month names
hourstringHour label
minutestringMinute label
applystringApply button text
selectDatestringDate placeholder
selectTimestringTime placeholder

FloatingInput

Input with floating label animation.

import { FloatingInput, FloatingTextarea } from "@elcto/ui/components";
import { Mail, Lock } from "lucide-react";

<FloatingInput
label="Email Address"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
icon={<Mail className="w-4 h-4" />}
error={errors.email}
/>

<FloatingInput
label="Password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
icon={<Lock className="w-4 h-4" />}
/>

<FloatingTextarea
label="Description"
value={description}
onChange={(e) => setDescription(e.target.value)}
rows={4}
/>

Props:

PropTypeDefaultDescription
labelstringrequiredInput label
iconReactNode-Left icon
errorstring-Error message
valuestring-Input value
onChange(e) => void-Change handler

Also accepts all standard <input> HTML attributes.


Input

Basic text input with icon support and error states.

import { Input } from "@elcto/ui/components";
import { Search, Mail, Lock } from "lucide-react";

// Basic input
<Input
placeholder="Enter your name..."
value={name}
onChange={(e) => setName(e.target.value)}
/>

// With left icon
<Input
leftIcon={<Search className="w-4 h-4" />}
placeholder="Search..."
/>

// With right icon
<Input
rightIcon={<Mail className="w-4 h-4" />}
placeholder="Email address..."
/>

// Size variants
<Input placeholder="Small" inputSize="sm" />
<Input placeholder="Medium" inputSize="md" />
<Input placeholder="Large" inputSize="lg" />

// Error state
<Input
error
errorMessage="This field is required"
placeholder="Required field"
/>

// Disabled
<Input placeholder="Disabled" disabled />

Props:

PropTypeDefaultDescription
leftIconReactNode-Left icon element
rightIconReactNode-Right icon element
inputSize'sm' | 'md' | 'lg''md'Size variant
errorbooleanfalseError state
errorMessagestring-Error message (also sets error state)

Also accepts all standard <input> HTML attributes.


LanguageSwitcher

Locale selection dropdown with flag icons.

import { LanguageSwitcher, GBFlag, DEFlag } from "@elcto/ui/components";
import { useTranslations } from "next-intl";

const t = useTranslations("common");

// Basic usage
<LanguageSwitcher
locale={locale}
onLocaleChange={(newLocale) => router.push(pathname, { locale: newLocale })}
/>

// Custom languages and translations
const languages = [
{ code: "en", nativeLabel: "English", Flag: GBFlag },
{ code: "de", nativeLabel: "Deutsch", Flag: DEFlag },
];

<LanguageSwitcher
locale={locale}
onLocaleChange={handleLocaleChange}
languages={languages}
headerLabel={t("language")}
getLanguageLabel={(code) => t(`languages.${code}`)}
/>

Props:

PropTypeDefaultDescription
localestringrequiredCurrent locale code
onLocaleChange(locale: string) => voidrequiredLocale change callback
languagesLanguage[][en, de]Available languages
headerLabelstring'Language'Header text
getLanguageLabel(code: string) => string-Translation function
classNamestring-Additional CSS classes

Language:

PropTypeDescription
codestringLocale code (e.g., "en", "de")
nativeLabelstringNative language name
FlagReact.ComponentTypeFlag icon component

Exported Icons:

  • GBFlag - British flag
  • DEFlag - German flag
  • GlobeIcon - Globe icon

LoadingSpinner & Spinner

Loading indicator components.

import { LoadingSpinner, Spinner } from "@elcto/ui/components";

// LoadingSpinner - Branded loading with logo animation
<LoadingSpinner />
<LoadingSpinner size="sm" />
<LoadingSpinner size="lg" />

// Full page loading
<div className="flex items-center justify-center min-h-screen">
<LoadingSpinner size="lg" />
</div>

// Spinner - Simple animated spinner
<Spinner />
<Spinner size="sm" />
<Spinner size="lg" />
<Spinner className="text-brand" />

LoadingSpinner Props:

PropTypeDefaultDescription
size'sm' | 'md' | 'lg''md'Spinner size
classNamestring-Additional CSS classes

Spinner Props:

PropTypeDefaultDescription
size'xs' | 'sm' | 'md' | 'lg''md'Spinner size
classNamestring-Additional CSS classes

Size Reference:

SizeLoadingSpinnerSpinner
xs-w-3 h-3
smsmallw-4 h-4
mdmediumw-5 h-5
lglargew-6 h-6

Dialog windows with composable structure.

import {
Modal,
ModalHeader,
ModalBody,
ModalFooter,
InfoModal,
ConfirmModal,
} from "@elcto/ui/components";

// Basic modal
<Modal isOpen={isOpen} onClose={() => setIsOpen(false)} title="Edit Profile">
<ModalBody>
<p>Modal content goes here.</p>
</ModalBody>
<ModalFooter>
<Button variant="secondary" onClick={() => setIsOpen(false)}>Cancel</Button>
<Button onClick={handleSave}>Save</Button>
</ModalFooter>
</Modal>

// Info modal
<InfoModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Success!"
description="Your changes have been saved."
icon={<CheckCircle className="w-8 h-8 text-brand" />}
buttonText="Done"
/>

// Confirm modal
<ConfirmModal
isOpen={isOpen}
onClose={() => setIsOpen(false)}
onConfirm={handleDelete}
title="Delete Item"
message="Are you sure you want to delete this item? This action cannot be undone."
confirmText="Delete"
cancelText="Cancel"
variant="danger"
icon={<Trash2 className="w-6 h-6 text-[var(--error)]" />}
/>

Modal Props:

PropTypeDefaultDescription
isOpenbooleanrequiredOpen state
onClose() => voidrequiredClose callback
titlestring-Modal title
iconReactNode-Header icon
maxWidth'sm' | 'md' | 'lg' | 'xl' | '2xl''md'Max width
showCloseButtonbooleantrueShow X button
closeOnBackdropbooleantrueClose on backdrop click
closeOnEscapebooleantrueClose on Escape key

ConfirmModal Props:

PropTypeDefaultDescription
isOpenbooleanrequiredOpen state
onClose() => voidrequiredClose callback
onConfirm() => voidrequiredConfirm callback
titlestringrequiredModal title
messageReactNoderequiredConfirmation message
confirmTextstring'Confirm'Confirm button text
cancelTextstring'Cancel'Cancel button text
variant'danger' | 'warning' | 'info' | 'default''default'Button style
isLoadingbooleanfalseLoading state

Table

Comprehensive data table system with container, loading states, pagination, and list layout support.

Basic Table

import {
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
} from "@elcto/ui/components";

<Table>
<TableHeader>
<TableRow>
<TableHead>Name</TableHead>
<TableHead>Email</TableHead>
<TableHead>Status</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.name}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Badge variant={user.active ? "success" : "neutral"}>
{user.active ? "Active" : "Inactive"}
</Badge>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
import {
TableContainer,
TableHeaderBar,
TableFooterBar,
TableWrapper,
Table,
TableHeader,
TableBody,
TableRow,
TableHead,
TableCell,
TableEmpty,
TableCenteredLoading,
TablePagination,
} from "@elcto/ui/components";
import { History, Loader2, ChevronLeft, ChevronRight, ArrowUp, ArrowDown, ArrowUpDown } from "lucide-react";

<TableContainer>
{/* Header with title */}
<TableHeaderBar>
<div className="flex items-center gap-3">
<History className="w-5 h-5 text-brand" />
<span className="font-semibold text-primary">Activity Log</span>
</div>
</TableHeaderBar>

{/* Loading state */}
{isLoading ? (
<TableCenteredLoading
icon={<Loader2 className="w-5 h-5 animate-spin text-muted" />}
text="Loading..."
/>
) : events.length === 0 ? (
/* Empty state */
<TableEmpty
icon={<History className="w-6 h-6 text-muted" />}
title="No events found"
description="Try adjusting your filters"
/>
) : (
/* Table content */
<TableWrapper>
<Table>
<TableHeader>
<TableRow>
<TableHead
sortable
sorted={sortField === "event" ? sortDirection : null}
onSort={() => handleSort("event")}
sortIcon={<ArrowUpDown className="w-3 h-3 opacity-40" />}
sortAscIcon={<ArrowUp className="w-3 h-3" />}
sortDescIcon={<ArrowDown className="w-3 h-3" />}
>
Event
</TableHead>
<TableHead>User</TableHead>
<TableHead
sortable
sorted={sortField === "date" ? sortDirection : null}
onSort={() => handleSort("date")}
sortIcon={<ArrowUpDown className="w-3 h-3 opacity-40" />}
sortAscIcon={<ArrowUp className="w-3 h-3" />}
sortDescIcon={<ArrowDown className="w-3 h-3" />}
>
Date
</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{events.map((event) => (
<TableRow
key={event.id}
clickable
onClick={() => router.push(`/audit/${event.id}`)}
>
<TableCell>{event.type}</TableCell>
<TableCell>{event.user}</TableCell>
<TableCell>{event.date}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableWrapper>
)}

{/* Pagination footer */}
{totalPages > 1 && (
<TableFooterBar>
<TablePagination
page={page}
totalPages={totalPages}
totalCount={totalCount}
limit={limit}
onPageChange={setPage}
showingText="Showing {start}-{end} of {total}"
pageText="Page {current} of {total}"
prevIcon={<ChevronLeft className="w-4 h-4" />}
nextIcon={<ChevronRight className="w-4 h-4" />}
/>
</TableFooterBar>
)}
</TableContainer>

List Layout (Card-Based)

For activity feeds and card-based lists:

import {
TableContainer,
TableHeaderBar,
TableList,
TableListItem,
TableListGroupHeader,
TableEmpty,
} from "@elcto/ui/components";

<TableContainer>
<TableHeaderBar>
<h2 className="font-semibold">Recent Activity</h2>
</TableHeaderBar>

<TableList>
{groupedEvents.map((group) => (
<div key={group.date}>
{/* Sticky date header */}
<TableListGroupHeader>
{group.label}
</TableListGroupHeader>

{/* Events in group */}
{group.events.map((event) => (
<TableListItem
key={event.id}
clickable
onClick={() => toggleExpand(event.id)}
>
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-[var(--bg-3)]" />
<div>
<p className="font-medium">{event.title}</p>
<p className="text-sm text-muted">{event.description}</p>
</div>
</div>
</TableListItem>
))}
</div>
))}
</TableList>
</TableContainer>

Component Reference

Container Components

TableContainer - Card-styled wrapper with border and rounded corners.

PropTypeDefaultDescription
childrenReactNoderequiredTable content
classNamestring-Additional CSS classes

TableHeaderBar - Header section above the table (for titles, filters).

PropTypeDefaultDescription
childrenReactNoderequiredHeader content
stickybooleanfalseSticky positioning
classNamestring-Additional CSS classes

TableFooterBar - Footer section below the table (for pagination).

PropTypeDefaultDescription
childrenReactNoderequiredFooter content
classNamestring-Additional CSS classes

TableWrapper - Scrollable container for horizontal overflow.

PropTypeDefaultDescription
childrenReactNoderequiredTable element
classNamestring-Additional CSS classes

Core Table Components

Table - Main table element.

PropTypeDefaultDescription
childrenReactNoderequiredTable content
classNamestring-Additional CSS classes

TableHeader - Table thead with background styling.

PropTypeDefaultDescription
childrenReactNoderequiredHeader rows
classNamestring-Additional CSS classes

TableBody - Table tbody with row dividers.

PropTypeDefaultDescription
childrenReactNoderequiredBody rows
classNamestring-Additional CSS classes

TableRow - Table row with hover and click states.

PropTypeDefaultDescription
childrenReactNoderequiredRow cells
hoverablebooleantrueEnable hover effect
clickablebooleanfalseEnable cursor pointer
classNamestring-Additional CSS classes

TableHead - Header cell with sortable support.

PropTypeDefaultDescription
childrenReactNoderequiredCell content
sortablebooleanfalseEnable sorting
sorted'asc' | 'desc' | null-Current sort state
onSort() => void-Sort click handler
sortIconReactNode-Default sort icon
sortAscIconReactNode-Ascending sort icon
sortDescIconReactNode-Descending sort icon
classNamestring-Additional CSS classes

TableCell - Data cell.

PropTypeDefaultDescription
childrenReactNoderequiredCell content
classNamestring-Additional CSS classes

State Components

TableEmpty - Empty state display.

PropTypeDefaultDescription
iconReactNode-Icon element
titlestringrequiredTitle text
descriptionstring-Description text
classNamestring-Additional CSS classes

TableLoading - Skeleton loading rows.

PropTypeDefaultDescription
rowsnumber5Number of skeleton rows
columnsnumber4Number of columns
classNamestring-Additional CSS classes

TableCenteredLoading - Centered spinner loading.

PropTypeDefaultDescription
iconReactNode-Loading spinner icon
textstring-Loading text
classNamestring-Additional CSS classes

TablePagination - Pagination controls.

PropTypeDefaultDescription
pagenumberrequiredCurrent page (1-indexed)
totalPagesnumberrequiredTotal number of pages
totalCountnumberrequiredTotal number of items
limitnumberrequiredItems per page
onPageChange(page: number) => voidrequiredPage change callback
showingTextstring-Text with {start}, {end}, {total} placeholders
pageTextstring-Text with {current}, {total} placeholders
prevIconReactNode-Previous button icon
nextIconReactNode-Next button icon
classNamestring-Additional CSS classes

List Layout Components

TableList - Divider-separated list container.

PropTypeDefaultDescription
childrenReactNoderequiredList items
classNamestring-Additional CSS classes

TableListItem - List item with hover/click states.

PropTypeDefaultDescription
childrenReactNoderequiredItem content
clickablebooleanfalseEnable cursor pointer
hoverablebooleantrueEnable hover effect
classNamestring-Additional CSS classes

TableListGroupHeader - Sticky group header (for date groupings).

PropTypeDefaultDescription
childrenReactNoderequiredHeader content
stickybooleantrueSticky positioning
classNamestring-Additional CSS classes

Toast

Toast notification system with auto-dismiss and slide animations.

import { ToastProvider, useToast } from "@elcto/ui/components";

// Wrap your app with ToastProvider
function App() {
return (
<ToastProvider
position="top-right"
autoDismissMs={5000}
animationDurationMs={200}
topOffset="4rem"
>
<MyApp />
</ToastProvider>
);
}

// Use the hook in any component
function MyComponent() {
const { showSuccess, showError, showWarning, showInfo, showToast } = useToast();

const handleSave = async () => {
try {
await saveData();
showSuccess("Changes saved successfully!", "Success");
} catch (error) {
showError("Failed to save changes. Please try again.");
}
};

return (
<div>
<button onClick={() => showInfo("This is an info message")}>
Show Info
</button>
<button onClick={() => showWarning("This is a warning", "Warning")}>
Show Warning
</button>
<button onClick={handleSave}>Save</button>
</div>
);
}

Features:

  • Auto-dismiss after configurable duration
  • Slide-in/slide-out animations based on position
  • Backdrop blur effect
  • Manual dismiss with close button
  • Multiple toast variants (success, error, warning, info)

ToastProvider Props:

PropTypeDefaultDescription
childrenReactNoderequiredApp content
position'top-center' | 'top-right' | 'bottom-center' | 'bottom-right''top-center'Toast position
autoDismissMsnumber5000Auto-dismiss duration in milliseconds
animationDurationMsnumber200Slide animation duration in milliseconds
topOffsetstring-Top offset (e.g., "4rem" for header height)
classNamestring-Additional CSS classes for the container

useToast Hook:

MethodSignatureDescription
showToast(message, variant?, title?) => voidShow any variant
showSuccess(message, title?) => voidShow success toast
showError(message, title?) => voidShow error toast
showWarning(message, title?) => voidShow warning toast
showInfo(message, title?) => voidShow info toast

Animation Behavior:

  • top-center: Slides in from top, slides out to top
  • top-right: Slides in from right, slides out to right
  • bottom-center: Slides in from bottom, slides out to bottom
  • bottom-right: Slides in from right, slides out to right

Tooltip

Hover tooltips with positioning.

import { Tooltip } from "@elcto/ui/components";

<Tooltip content="Click to edit" position="top">
<button>Edit</button>
</Tooltip>

<Tooltip content="More info here" position="right" variant="info">
<InfoIcon />
</Tooltip>

Props:

PropTypeDefaultDescription
contentReactNoderequiredTooltip content
position'top' | 'right' | 'bottom' | 'left''top'Tooltip position
variant'default' | 'info''default'Tooltip style
childrenReactNoderequiredTrigger element
classNamestring-Additional CSS classes

Brand logo components.

import { ElctoLogo, ElcapitanoLogo } from "@elcto/ui/components";

<ElctoLogo />
<ElctoLogo className="w-32" />

<ElcapitanoLogo />
<ElcapitanoLogo className="w-40" />

Props (both components):

PropTypeDefaultDescription
classNamestring-Additional CSS classes

Platform Icons

SVG icon components for various platforms.

import {
TwitchIcon,
DiscordIcon,
GitHubIcon,
YouTubeIcon,
KickIcon,
SteamIcon,
TrovoIcon,
TwitterIcon,
} from "@elcto/ui/components";

// All icons accept className for sizing and color
<TwitchIcon className="w-5 h-5" />
<DiscordIcon className="w-5 h-5 text-[#5865F2]" />
<GitHubIcon className="w-5 h-5" />
<YouTubeIcon className="w-5 h-5 text-[#FF0000]" />
<KickIcon className="w-5 h-5 text-[#53FC18]" />
<SteamIcon className="w-5 h-5" />
<TrovoIcon className="w-5 h-5" />
<TwitterIcon className="w-5 h-5" />

Available Icons:

ComponentPlatform
TwitchIconTwitch
DiscordIconDiscord
GitHubIconGitHub
YouTubeIconYouTube
KickIconKick
SteamIconSteam
TrovoIconTrovo
TwitterIconTwitter/X

Props (all icons):

PropTypeDefaultDescription
classNamestring-CSS classes for sizing and color

Select

Custom dropdown select component with keyboard navigation and auto-sizing.

import { Select } from "@elcto/ui/components";
import { Globe } from "lucide-react";

const options = [
{ value: "en", label: "English" },
{ value: "de", label: "German" },
{ value: "fr", label: "French", disabled: true },
];

// Basic usage
<Select
options={options}
value={selected}
onChange={setSelected}
placeholder="Select language..."
/>

// With icon and size variant
<Select
options={options}
value={selected}
onChange={setSelected}
leftIcon={<Globe className="w-4 h-4" />}
size="lg"
/>

// Error state
<Select
options={options}
value={selected}
onChange={setSelected}
error={true}
/>

// Fixed width (disable auto-sizing)
<Select
options={options}
value={selected}
onChange={setSelected}
autoWidth={false}
containerClassName="w-48"
/>

Props:

PropTypeDefaultDescription
optionsSelectOption[]requiredOptions to display
valuestring-Currently selected value
onChange(value: string) => void-Change callback
placeholderstring'Select...'Placeholder text
size'sm' | 'md' | 'lg''md'Size variant
errorbooleanfalseError state
disabledbooleanfalseDisabled state
leftIconReactNode-Icon on the left
autoWidthbooleantrueAuto-size to fit longest option
namestring-Name for form submission
classNamestring-Classes for trigger button
containerClassNamestring-Classes for container

SelectOption:

PropTypeDescription
valuestringOption value
labelstringDisplay label
disabledbooleanWhether option is disabled

Features:

  • Full keyboard navigation (Arrow keys, Enter, Escape)
  • Auto-sizing to fit longest option label
  • Smooth open/close animations
  • Accessible with ARIA attributes
  • Form submission support with hidden input

Nested navigation component for secondary navigation within pages.

import { SubNav } from "@elcto/ui/components";
import { Settings, Shield, Link2 } from "lucide-react";

const items = [
{ key: "settings", label: "Settings", icon: <Settings className="w-4 h-4" /> },
{ key: "security", label: "Security", icon: <Shield className="w-4 h-4" /> },
{ key: "connections", label: "Connections", icon: <Link2 className="w-4 h-4" /> },
];

// Basic usage
<SubNav
items={items}
activeKey={currentTab}
onItemClick={setCurrentTab}
/>

// Small size
<SubNav
items={items}
activeKey={currentTab}
onItemClick={setCurrentTab}
size="sm"
/>

// With custom Link component (Next.js)
import { Link } from "next/link";

<SubNav
items={items}
activeKey={currentPath}
renderItem={(item, isActive, className) => (
<Link key={item.key} href={`/account/${item.key}`} className={className}>
{item.icon}
{item.label}
</Link>
)}
/>

Props:

PropTypeDefaultDescription
itemsSubNavItem[]requiredNavigation items
activeKeystringrequiredCurrently active item key
onItemClick(key: string) => void-Click callback
renderItem(item, isActive, className) => ReactNode-Custom render function
size'sm' | 'md''md'Size variant
classNamestring-Additional CSS classes

SubNavItem:

PropTypeDescription
keystringUnique identifier
labelstringDisplay label
iconReactNodeOptional icon
exactbooleanUse exact matching for active state

A horizontal sub-navigation bar that appears below the header. Respects sidebar width (collapsed/expanded) in dashboard layouts. On mobile, shows a dropdown menu or horizontal scroll.

import { SubMenuBar, type SubMenuBarItem } from "@elcto/ui/components";
import { History, Flag } from "lucide-react";

const menuItems: SubMenuBarItem[] = [
{
key: "overview",
label: "Overview",
href: "/system/audit",
icon: <History className="w-4 h-4" />,
},
{
key: "reports",
label: "Reports",
href: "/system/audit/reports",
icon: <Flag className="w-4 h-4" />,
},
];

// Basic usage
<SubMenuBar
items={menuItems}
activeHref={pathname}
sidebarCollapsed={sidebarCollapsed}
hasBanner={showBanner}
/>

// With Next.js Link
import Link from "next/link";

<SubMenuBar
items={menuItems}
activeHref={pathname}
sidebarCollapsed={sidebarCollapsed}
hasBanner={hasBanner}
renderItem={(item, isActive, className) => (
<Link key={item.key} href={item.href} className={className}>
{item.icon}
{item.label}
</Link>
)}
/>

// Horizontal scroll variant on mobile (instead of dropdown)
<SubMenuBar
items={menuItems}
activeHref={pathname}
mobileVariant="scroll"
/>

Props:

PropTypeDefaultDescription
itemsSubMenuBarItem[]requiredNavigation items
activeKeystring-Active item by key
activeHrefstring-Active item by href (alternative to activeKey)
renderItem(item, isActive, className) => ReactNode-Custom render function for items
classNamestring-Additional CSS classes
sidebarCollapsedbooleanfalseWhether sidebar is collapsed (affects left offset on desktop)
hasBannerbooleanfalseWhether a banner is showing above (affects top position)
heightstring'3.5rem'Custom height for the bar
mobileVariant'dropdown' | 'scroll''dropdown'Mobile menu variant

SubMenuBarItem:

PropTypeDescription
keystringUnique identifier
labelstringDisplay label
hrefstringURL/href for the item
iconReactNodeOptional icon

Mobile Behavior:

  • Dropdown (default): Shows active item with chevron, clicking opens a dropdown with all items
  • Scroll: Shows all items in a horizontal scrollable row

Desktop Behavior:

  • Respects sidebar width: 16rem when expanded, 4rem when collapsed
  • Smooth 200ms transition when sidebar state changes
  • Fixed position below header (4rem from top, or 6.5rem with banner)

Layout Integration Example:

// In your layout component
import { SubMenuBar } from "@elcto/ui/components";
import { useWorkspace } from "@/contexts/WorkspaceContext";
import { useApiHealth, useShowApiHealthBanner } from "@elcto/ui/components";

export default function AuditLayout({ children }) {
const { sidebarCollapsed } = useWorkspace();
const { state: apiHealth } = useApiHealth();
const hasBanner = useShowApiHealthBanner(apiHealth);

return (
<>
<SubMenuBar
items={menuItems}
activeHref={pathname}
sidebarCollapsed={sidebarCollapsed}
hasBanner={hasBanner}
renderItem={(item, isActive, className) => (
<Link href={item.href} className={className}>
{item.icon}
{item.label}
</Link>
)}
/>
{/* Add padding for the fixed SubMenuBar */}
<div className="pt-14">
{children}
</div>
</>
);
}

AnimatedBackground

Animated background component with gradient circles and floating particles. Ideal for auth pages, error pages, or any page needing visual interest.

import {
AnimatedBackground,
AnimatedBackgroundProvider,
SetAnimatedBackground,
useAnimatedBackground,
} from "@elcto/ui/components";

// Basic usage
<AnimatedBackground variant="brand">
<div className="p-8">
<h1>Welcome</h1>
</div>
</AnimatedBackground>

// Different color variants
<AnimatedBackground variant="error">
<ErrorPage />
</AnimatedBackground>

<AnimatedBackground variant="warning">
<WarningPage />
</AnimatedBackground>

<AnimatedBackground variant="success">
<SuccessPage />
</AnimatedBackground>

// Custom color
<AnimatedBackground variant="custom" customColor="#ff6b6b">
<CustomPage />
</AnimatedBackground>

// Disable certain effects
<AnimatedBackground
variant="brand"
showParticles={false}
showSpinningGradient={false}
particleCount={6}
>
<MinimalPage />
</AnimatedBackground>

// Fixed overlay
<AnimatedBackground variant="error" fixed zIndex={100}>
<FullScreenOverlay />
</AnimatedBackground>

AnimatedBackground Props:

PropTypeDefaultDescription
variant'brand' | 'warning' | 'error' | 'success' | 'subtle' | 'custom''brand'Color variant
customColorstring-Custom CSS color (when variant="custom")
particleCountnumber12Number of floating particles
showParticlesbooleantrueShow floating particles
showGradientsbooleantrueShow gradient circles
showSpinningGradientbooleantrueShow spinning gradient
fixedbooleanfalseUse fixed positioning (for overlays)
zIndexnumber50Z-index when fixed
useContextConfigbooleanfalseRead config from context
classNamestring-Additional CSS classes

Context Provider Pattern

For layouts where child pages need to override the background:

// In your layout.tsx
import {
AnimatedBackground,
AnimatedBackgroundProvider,
} from "@elcto/ui/components";

export default function AuthLayout({ children }) {
return (
<AnimatedBackgroundProvider defaultVariant="brand">
<AnimatedBackground useContextConfig>
{children}
</AnimatedBackground>
</AnimatedBackgroundProvider>
);
}

// In a child page (e.g., banned/layout.tsx)
import { SetAnimatedBackground } from "@elcto/ui/components";

export default function BannedLayout({ children }) {
return (
<SetAnimatedBackground variant="error">
{children}
</SetAnimatedBackground>
);
}

AnimatedBackgroundProvider Props:

PropTypeDefaultDescription
defaultVariantstring'brand'Default color variant
defaultsPartial<AnimatedBackgroundConfig>-Default configuration

useAnimatedBackground Hook:

const { config, setConfig, resetConfig } = useAnimatedBackground();

// Update background dynamically
useEffect(() => {
setConfig({ variant: "error", showParticles: false });
return () => resetConfig();
}, []);

Turnstile

Cloudflare Turnstile CAPTCHA integration for bot protection.

import { Turnstile, useTurnstile } from "@elcto/ui/components";

// Using the hook (recommended)
function LoginForm() {
const turnstile = useTurnstile();
const [isLoading, setIsLoading] = useState(false);

const handleSubmit = async (e: FormEvent) => {
e.preventDefault();

if (turnstile.isWaiting) {
setError("Please wait for verification...");
return;
}

setIsLoading(true);
await fetch("/api/login", {
method: "POST",
body: JSON.stringify({
email,
password,
turnstile_token: turnstile.getTokenForApi(),
}),
});
};

return (
<form onSubmit={handleSubmit}>
<input
type="email"
disabled={turnstile.disabledWhileWaiting}
/>
<input
type="password"
disabled={turnstile.disabledWhileWaiting}
/>

{/* Render the invisible widget */}
<turnstile.Widget />

<button disabled={isLoading || turnstile.disabledWhileWaiting}>
{turnstile.isWaiting ? "Verifying..." : "Sign In"}
</button>
</form>
);
}

// Direct component usage
<Turnstile
siteKey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY}
onVerify={(token) => setToken(token)}
onError={() => setError("Verification failed")}
onExpire={() => setToken(null)}
theme="dark"
/>

Turnstile Props:

PropTypeDefaultDescription
siteKeystringrequiredCloudflare Turnstile site key
onVerify(token: string) => voidrequiredSuccess callback
onError() => void-Error callback
onExpire() => void-Token expiry callback
theme'light' | 'dark' | 'auto''auto'Widget theme
size'normal' | 'compact''normal'Widget size
classNamestring-Additional CSS classes

useTurnstile Hook:

const turnstile = useTurnstile({
theme: "dark",
siteKey: "your-site-key", // or use NEXT_PUBLIC_TURNSTILE_SITE_KEY env var
});

Hook Options:

OptionTypeDefaultDescription
theme'light' | 'dark' | 'auto''auto'Widget theme
siteKeystringenv varTurnstile site key

Hook Return Value:

PropertyTypeDescription
tokenstring | nullCurrent verification token
isEnabledbooleanWhether Turnstile is configured
isWaitingbooleanWaiting for verification
isVerifiedbooleanVerification complete
reset() => voidReset token state
disabledWhileWaitingbooleanUse for disabling inputs
WidgetReact.FCWidget component to render
getTokenForApi() => string | undefinedGet token for API calls

Environment Variable:

Set NEXT_PUBLIC_TURNSTILE_SITE_KEY in your .env file:

NEXT_PUBLIC_TURNSTILE_SITE_KEY=your-cloudflare-site-key

If the environment variable is not set, Turnstile is automatically disabled (isEnabled = false).


FileUpload

Complete file upload component with drag-and-drop, presigned URL support, progress tracking, and file previews.

import { FileUpload } from "@elcto/ui/components";

// With presigned URLs for direct S3 upload
<FileUpload
accept="image/*,video/*"
maxSize={5 * 1024 * 1024} // 5MB
maxFiles={5}
directUpload
presignedUrlFetcher={async (file) => {
const res = await fetch("/api/storage/presigned-upload", {
method: "POST",
body: JSON.stringify({
filename: file.name,
contentType: file.type,
category: file.type.startsWith("image/") ? "images" : "videos",
}),
});
return res.json();
}}
onUpload={(files) => console.log("Uploaded:", files)}
onError={(error) => showToast(error.message, "error")}
label="Drop files here"
description="or click to browse"
/>

// Without direct upload (for form handling)
<FileUpload
accept="image/*"
maxFiles={1}
onUpload={(files) => setSelectedFile(files[0])}
variant="compact"
/>

// Controlled mode
<FileUpload
files={files}
onFilesChange={setFiles}
directUpload
presignedUrlFetcher={fetchPresignedUrl}
onUpload={handleUpload}
/>

FileUpload Props:

PropTypeDefaultDescription
onUpload(files: UploadedFile[]) => voidrequiredUpload success callback
onError(error: UploadError) => void-Error callback
presignedUrlFetcher(file: File) => Promise<PresignedUploadUrl>-Function to get presigned URL
directUploadbooleanfalseEnable direct browser upload to S3
acceptstring-MIME types to accept (e.g., "image/,video/")
maxSizenumber-Max file size in bytes
maxFilesnumber10Max number of files
variant'default' | 'compact' | 'inline''default'Dropzone style
size'sm' | 'md' | 'lg''md'Size variant
showPreviewbooleantrueShow file previews
previewType'grid' | 'list''grid'Preview layout
labelstring-Dropzone label
descriptionstring-Dropzone description
iconReactNode-Custom dropzone icon
childrenReactNode-Custom dropzone content
disabledbooleanfalseDisable upload
loadingbooleanfalseLoading state
filesUploadedFile[]-Controlled files (for controlled mode)
onFilesChange(files: UploadedFile[]) => void-Files change callback (controlled mode)
classNamestring-Additional CSS classes

PresignedUploadUrl:

interface PresignedUploadUrl {
url: string; // The presigned PUT URL
key: string; // The S3 object key
expiresAt: string; // Expiration timestamp
}

UploadedFile:

interface UploadedFile {
key: string; // File key (S3 key or temp ID)
url: string; // Public URL after upload
name: string; // Original filename
size: number; // File size in bytes
type: string; // MIME type
progress?: number; // Upload progress 0-100
status: "pending" | "uploading" | "complete" | "error";
error?: string; // Error message if status is "error"
}

UploadError:

interface UploadError {
file: File;
code: "SIZE_EXCEEDED" | "TYPE_NOT_ALLOWED" | "MAX_FILES" | "UPLOAD_FAILED";
message: string;
}

Sub-components:

ComponentDescription
FileDropzoneDrag-and-drop zone with validation
FilePreviewGrid/list preview of uploaded files
UploadProgressUpload progress bar

ImagePreview

Display images with loading states, error handling, and optional lightbox support.

import { ImagePreview } from "@elcto/ui/components";

// Basic usage
<ImagePreview
src="https://storage.example.com/images/photo.jpg"
alt="User photo"
aspectRatio="16:9"
objectFit="cover"
rounded="md"
/>

// With lightbox (click to view fullscreen)
<ImagePreview
src={imageUrl}
alt="Gallery image"
aspectRatio="1:1"
lightbox
zoom
/>

// Custom fallback on error
<ImagePreview
src={userAvatar}
alt={userName}
fallback={<Avatar name={userName} size={120} />}
rounded="full"
/>

// Lazy loading disabled
<ImagePreview
src={heroImage}
alt="Hero"
lazy={false}
aspectRatio="21:9"
/>

ImagePreview Props:

PropTypeDefaultDescription
srcstringrequiredImage source URL
altstringrequiredAlt text for accessibility
widthnumber | string-Image width
heightnumber | string-Image height
aspectRatio'1:1' | '4:3' | '16:9' | 'auto''auto'Aspect ratio
objectFit'cover' | 'contain' | 'fill''cover'How image fills container
lightboxbooleanfalseEnable fullscreen view on click
zoombooleanfalseEnable zoom on hover/click
lazybooleantrueEnable lazy loading
fallbackstring | ReactNode-Error fallback (URL or component)
placeholderReactNode-Loading placeholder
rounded'none' | 'sm' | 'md' | 'lg' | 'full''md'Border radius
borderbooleanfalseShow border
shadowbooleanfalseAdd shadow
onClick() => void-Click handler (overrides lightbox)
classNamestring-Additional CSS classes

Loading State:

The component shows a shimmer skeleton while loading. Customize with the placeholder prop.

Error State:

When image fails to load, shows a fallback. Can be a URL string or React component.

Lightbox:

When lightbox is true, clicking the image opens a fullscreen overlay with close button and optional zoom controls.