@digitwhale_innovations/uhuru-ui
v0.2.61
Published
Digitwhale Innovations' source-first React component library for Uhuru products.
Downloads
11,118
Maintainers
Readme
Uhuru UI
Uhuru UI is Digitwhale Innovations' React component library for Uhuru products.
It is intended for external React apps as well as internal product work.
Can A New App Use It?
Yes.
A brand-new React app can install the published package and use it immediately, as long as:
- the app uses React 17, 18, or 19
- the app has a modern bundler such as Vite, Next.js, or another build tool that can consume ESM/TypeScript-style package source
- you import the Uhuru stylesheet once
- you wrap the app in
UhuruProvider
Install
npm install @digitwhale_innovations/uhuru-uiIf you want to pin a specific release:
npm install @digitwhale_innovations/[email protected]Minimal React Setup
// src/main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { UhuruProvider } from "@digitwhale_innovations/uhuru-ui";
import "@digitwhale_innovations/uhuru-ui/styles.css";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<UhuruProvider theme="light" density="compact">
<App />
</UhuruProvider>
</React.StrictMode>,
);// src/App.tsx
import { Button, Card, CardBody, CardHeader } from "@digitwhale_innovations/uhuru-ui";
export default function App() {
return (
<main style={{ padding: 24 }}>
<Card>
<CardHeader
eyebrow="Workspace"
title="Uhuru Billing"
description="A clean external React setup."
/>
<CardBody>
<Button>Save changes</Button>
</CardBody>
</Card>
</main>
);
}What You Need To Import
Always import components from the package root:
import { Button, Card, TextField } from "@digitwhale_innovations/uhuru-ui";Always import styles once:
import "@digitwhale_innovations/uhuru-ui/styles.css";Core Concepts
UhuruProvidercontrols theme, density, radius, preset accent selection, and shared app tokens.UhuruProvidercan also configure the built-in alert system globally.- Use
presetAccentto switch between the bundled accent presets or a custom preset key. - Use
accentPresetsto add or replace preset options without rebuilding component styles. - Use
colorswhen a product needs to refine or replace any token after the preset is resolved. - The package ships source-first exports, so a modern React bundler is the safest consumer setup.
- Styles live in a separate stylesheet and should be loaded once near the app entry.
Accent presets are theme-aware. A preset can define light and dark overrides, while colors remains the final escape hatch for product-specific token tweaks.
- The design system now ships with preset accents such as
black,brown,blue,teal,rose, andamber.
Markdown And Advanced Blocks
Use Markdown for assistant responses, user-authored content, documentation,
and formatted product copy. It renders code, math, diagrams, tables, lists,
media, files, and embeds with Uhuru surfaces. Code, math, and diagram blocks
include copy controls automatically.
import { Markdown } from "@digitwhale_innovations/uhuru-ui";
<Markdown
value={content}
onCopy={(value) => navigator.clipboard.writeText(value)}
/>Markdown supports these automatic advanced forms:
```steps
1. Install Uhuru | Add the published package.
2. Add the provider | Configure the shared defaults.
```
```checklist
- [x] Install package
- [ ] Add provider
```
```key-value
Theme: dark
Density: compact
```
```timeline
2026-08-09 | Released | Advanced Markdown blocks.
```
```file
guide.md | Markdown guide | /guide.md
```
```media
image | /product.png | Product preview
```
```embed
Open guide | https://example.com/guide
```The exported block components are CodeBlock, MathBlock, DiagramBlock,
CalloutBlock, QuoteBlock, TableBlock, StepsBlock, ChecklistBlock,
KeyValueBlock, TimelineBlock, FileBlock, MediaBlock, and EmbedBlock.
Use the components directly for typed API data and use fenced formats when
the source is already Markdown text.
Override semantic colors without replacing the defaults. Values can be shared across themes or supplied per theme:
<UhuruProvider
variant="glass"
presetAccent="teal"
accentPresets={{
graphite: {
accentSolid: "#334155",
accentStrong: "#1f2937",
actionPrimary: "#334155",
actionPrimaryHover: "#1f2937",
borderAccent: "#334155",
},
}}
colors={{
light: { bgBase: "#f7faf9" },
dark: { bgBase: "#071211", surfaceDefault: "#0d1f1d" },
}}
>
{children}
</UhuruProvider>Unset values continue using Uhuru's built-in light and dark palettes.
Use variant="glass" to apply Uhuru's translucent surface system across
cards, panels, controls, overlays, and data surfaces:
<UhuruProvider variant="glass" theme="dark" density="comfortable">
<App />
</UhuruProvider>The glass variant keeps the same component APIs, semantic colors, radius, and
density controls. It adds translucent surfaces, backdrop blur, softer borders,
and a subtle atmosphere. Use variant="default" or omit the prop for the
standard opaque Uhuru treatment.
Backgrounds
Use background on UhuruProvider when the application needs a consistent
pattern or media layer behind the interface. Uhuru includes four built-in
patterns: grid, cross, circuit, and dots. A background uses one source
at a time: video takes precedence over an image, an image takes precedence
over the bundled logo, and any media source disables the pattern. Backgrounds
are fixed to the viewport by default; use fixed: false when the asset should
move with the document.
<UhuruProvider
background={{
pattern: "circuit",
patternColor: "rgba(255, 255, 255, 0.06)",
patternOpacity: 0.9,
}}
>
<App />
</UhuruProvider>For a branded image layer, use an application asset or the built-in Uhuru
logo. blur, assetOpacity, overlay, overlayOpacity, position, and
size control the visual treatment without requiring custom page wrappers:
<UhuruProvider
background={{
logo: true,
logoBlur: 10,
logoOpacity: 0.14,
overlay: "#101111",
overlayOpacity: 0.72,
}}
>
<App />
</UhuruProvider>Use a video instead of a pattern or image when the background should move:
<UhuruProvider
background={{
video: { src: "/media/ambient.mp4", poster: "/media/ambient-poster.jpg" },
blur: 6,
assetOpacity: 0.35,
overlay: "#101111",
overlayOpacity: 0.58,
}}
>
<App />
</UhuruProvider>Side Panels
Use UhuruPanel when you want to control panel content yourself.
Render panel content through the matching leftPanel or rightPanel slot of
UhuruAppLayout:
import {
UhuruAppLayout,
UhuruPanel,
} from "@digitwhale_innovations/uhuru-ui";
<UhuruAppLayout
leftPanel={
<UhuruPanel
side="left"
config={{ header: <strong>Workspace</strong>, footer: <small>Signed in</small> }}
>
<nav>{/* your navigation */}</nav>
</UhuruPanel>
}
>
<main>{/* page content */}</main>
</UhuruAppLayout>;For grouped navigation, use UhuruGroupedPanel in the leftPanel slot.
AI panel
Use aiPanelConfig for a temporary AI workspace that can take over either side
of the app layout. When it closes, any normal panel on that side is restored
unchanged. The configuration extends PanelConfig, so resize, rail, collapse,
hover expansion, header, footer, and min/max sizing all behave consistently.
import {
AiPanel,
Button,
UhuruAppLayout,
useAiPanel,
} from "@digitwhale_innovations/uhuru-ui";
function CreateAgentAction() {
const aiPanel = useAiPanel();
return <Button onClick={aiPanel.open}>Create with AI</Button>;
}
<UhuruAppLayout
aiPanelConfig={{
enabled: true,
position: "left", // Defaults to left.
content: (
<AiPanel
messages={messages}
value={draft}
onChange={setDraft}
onSubmit={createAgent}
/>
),
ariaLabel: "AI agent builder",
allowResize: true,
collapsible: true,
defaultSize: 360,
minSize: 300,
maxSize: 560,
showRail: false,
header: <strong>Build an agent</strong>,
}}
leftPanel={<WorkspaceNavigation />}
>
<CreateAgentAction />
</UhuruAppLayout>;useAiPanel() returns open, close, toggle, isOpen, enabled, and
position. Calling it without an enabled, fully configured AI panel shows an
accessible error toast instead of throwing. On mobile, opening the AI panel
automatically opens the matching drawer.
Ungrouped custom navigation can restore route-owned content by passing a route
registry to UhuruPanel. The panel keeps rendering your own children; the
registry tells the layout which element to restore on first load:
<UhuruPanel
side="left"
defaultRoute="/dashboard/orders"
routes={[
{ path: "/dashboard/orders", element: <OrdersPage /> },
{ path: "/dashboard/customers", element: <CustomersPage /> },
]}
>
<nav>{/* your native links or buttons */}</nav>
</UhuruPanel>On refresh, the current browser path wins over defaultRoute. If no path
matches, the configured default or first route is opened. Omit routes when a
router such as React Router owns navigation.
import { useState } from "react";
import {
UhuruGroupedPanel,
UhuruAppLayout,
} from "@digitwhale_innovations/uhuru-ui";
function WorkspaceLayout() {
const [activeId, setActiveId] = useState("overview");
const groups = [
{
id: "workspace",
label: "Workspace",
items: [
{ id: "overview", label: "Overview" },
{ id: "activity", label: "Activity", badge: "3" },
],
},
{
id: "projects",
label: "Projects",
items: [
{
id: "uhuru",
label: "Uhuru",
children: [
{ id: "billing", label: "Billing" },
{ id: "settings", label: "Settings" },
],
},
],
},
];
return (
<UhuruAppLayout
leftPanel={
<UhuruGroupedPanel
activeId={activeId}
groups={groups}
onItemSelect={(item) => setActiveId(item.id)}
/>
}
>
<main>{/* render the active page here */}</main>
</UhuruAppLayout>
);
}activeId is controlled by the consuming page. Each item needs a unique id,
and selecting an item calls onItemSelect, where the app can update the
active page, route, or local view. For route-owned items, set
defaultRoute="/overview" or defaultRoute="overview"; without it, the
first item with both path and element opens automatically. No route opens
when the panel is empty, activeId is controlled, or a route is already active.
When the browser is refreshed on a matching item path, such as
/dashboard/orders, that path is restored before the default route is used.
The panel itself only renders navigation; it does not decide what the main
content should be for non-route items.
For route-level pages, panel items can own their route with path and
element. Put UhuruAppLayout outside the route content and the layout stays
mounted while the panel changes the active page:
Protect one route by wrapping its element:
const ordersRoute = {
path: "/dashboard/orders",
element: (
<UhuruRouteGuard
check={() => Boolean(session?.user)}
fallback={<SignInPage />}
redirectTo="/sign-in"
>
<OrdersPage />
</UhuruRouteGuard>
),
};Protect a whole group or shell by placing the guard around that composition
instead. allow is useful for a known boolean; check is useful for custom
logic. Use onDenied for logging or cleanup. The guard is synchronous and
router-agnostic; use a loading state in the parent while an asynchronous
session check is being resolved.
import { BrowserRouter } from "react-router-dom";
<BrowserRouter>
<UhuruAppLayout
leftPanel={
<UhuruGroupedPanel
groups={[
{
id: "workspace",
items: [
{
id: "overview",
label: "Overview",
path: "/overview",
element: <OverviewPage />,
},
{
id: "activity",
label: "Activity",
path: "/activity",
element: <ActivityPage />,
},
],
},
]}
/>
}
>
<main />
</UhuruAppLayout>
</BrowserRouter>;Set route: false for an item that should run its callback or toggle a local
control without becoming a panel route. Use href instead of path when the
router or browser should own navigation. The UI kit does not require a router
dependency.
The flat Sidebar accepts the same route entry shape through items:
<Sidebar
items={[
{
value: "overview",
label: "Overview",
path: "/overview",
element: <OverviewPage />,
},
{
value: "new-item",
label: "New item",
route: false,
onSelect: openCreateDialog,
},
]}
/>Resize behavior can be enabled independently:
<UhuruAppLayout
header={
<UhuruAppLayoutHeader
variant="classic"
profile={<Avatar src={user.image} name={user.name} size="md" />}
title="Uhuru Workspace"
description={workspace.name}
search={<SearchInput aria-label="Global search" placeholder="Search workspace" />}
actions={<WorkspaceActions />}
mobileActionItems={workspaceMenuItems}
leftTogglePlacement="header"
rightTogglePlacement="header"
height={56}
/>
}
leftPanel={<UhuruGroupedPanel groups={groups} />}
leftPanelConfig={{
allowResize: true,
collapsible: true,
expandIcon: <CaretDoubleRight />,
maxSize: 420,
minSize: 240,
}}
rightPanel={<UhuruPanel config={{ header: "Details" }} side="right">...</UhuruPanel>}
rightPanelConfig={{ allowResize: false, collapsible: true }}
>
{children}
</UhuruAppLayout>UhuruAppLayoutHeader has two structural variants. classic orders profile,
identity, search, and actions from left to right. standard places actions
first and the identity/profile group at the right. Panel toggles remain at the
outer edges in both variants. On mobile, search and non-panel actions move into
one overflow popover while both enabled panel toggles remain directly
accessible. Use mobileActionItems for menu-style actions or mobileActions
for custom popup content. Compact mode defaults to 1023px; customize it with
compactBreakpoint. The legacy leading, center, and trailing props remain
aliases for profile, search, and actions.
leftPanelConfig and rightPanelConfig share the same PanelConfig interface,
so each panel can independently opt into resizing, collapsing, sizing, and
custom icons without a second set of layout props. Set expandOnHover to
temporarily open a collapsed side rail while the pointer is over it, then
collapse it again when the pointer leaves. While temporarily expanded, the
collapse button icon and aria-expanded state also switch to match the visible
panel.
Use UhuruPanel for a standalone panel. Its required side prop accepts
left, right, or bottom:
import { UhuruPanel } from "@digitwhale_innovations/uhuru-ui";
<UhuruPanel side="bottom" config={{ header: "Terminal", collapsible: true }}>
{/* terminal content */}
</UhuruPanel>Header
Use UhuruHeader for a flexible responsive application header. The desktop
navigation is hidden on small screens and mobileMenu is revealed with an
icon button:
<UhuruHeader
brand={<strong>Uhuru Finance</strong>}
navigation={<><a href="/overview">Overview</a><a href="/reports">Reports</a></>}
actions={<Button size="compact">Create</Button>}
mobileMenu={<nav className="grid gap-2"><a href="/overview">Overview</a><a href="/reports">Reports</a></nav>}
sticky
/>Pass the header to UhuruAppLayout with its header prop when it should span
the application shell.
Flutter-Style Layout Primitives
Uhuru includes small web equivalents of Flutter's everyday composition
widgets. They render regular HTML elements and accept normal className,
style, and DOM props:
Form: semantic<form>with vertical spacing between fields.Row: horizontal flex layout.Column: vertical flex layout.Padding: edge-inset wrapper.Margin: outer-spacing wrapper.Container: existing centered width container.
Row and Column use Flutter's axis terminology. mainAxisSize="min" sizes
the layout to its content; the default "max" fills the available axis.
import {
Button,
Column,
Form,
Padding,
Row,
TextField,
} from "@digitwhale_innovations/uhuru-ui";
<Form onSubmit={handleSubmit}>
<Column gap={12}>
<TextField label="Workspace name" />
<Row
crossAxisAlignment="center"
mainAxisAlignment="spaceBetween"
wrap
>
<Padding padding={{ vertical: 4 }}>
<span>Required fields</span>
</Padding>
<Button type="submit">Save changes</Button>
</Row>
</Column>
</Form>;Padding and Margin accept a number, CSS size, or edge-inset object:
<Padding padding={{ all: 16 }} />
<Padding padding={{ horizontal: 24, vertical: 12 }} />
<Margin margin={{ top: 8, bottom: 24 }} />Available mainAxisAlignment values are start, center, end,
spaceBetween, spaceAround, and spaceEvenly. Available
crossAxisAlignment values are start, center, end, stretch, and
baseline.
Semantic spacing values are supported anywhere a layout spacing value is
accepted: xs (4px), sm (8px), md (12px), lg (16px), xl (24px), and
2xl (32px). Numeric values are pixels and CSS strings such as 1rem or
clamp(8px, 2vw, 24px) remain supported.
<Column gap="lg">
<Row gap="sm" mainAxisAlignment="spaceBetween" crossAxisAlignment="center">
<Text>Orders</Text>
<Button>New order</Button>
</Row>
<Padding padding={{ horizontal: "md", vertical: "sm" }}>
Content
</Padding>
</Column>Additional Flutter-guided helpers are available for composing responsive surfaces:
import {
AspectRatio,
Button,
EmptyState,
Expanded,
Positioned,
SizedBox,
Spacer,
Stack,
Visibility,
Wrap,
} from "@digitwhale_innovations/uhuru-ui";
<Row>
<Expanded>Primary content</Expanded>
<SizedBox width={12} />
<Spacer />
<Visibility visible={showActions}>
<Button>Continue</Button>
</Visibility>
</Row>
<Stack className="min-h-48">
<div>Base layer</div>
<Positioned right={16} top={16}>
<Badge>Live</Badge>
</Positioned>
</Stack>
<Wrap spacing={12} runSpacing={8}>
{tags.map((tag) => <Badge key={tag}>{tag}</Badge>)}
</Wrap>
<AspectRatio ratio={16 / 9}>Responsive content</AspectRatio>;Expanded, Flexible, and Spacer are intended as children of Row or
Column. Positioned is intended as a child of Stack. Visibility can
either remove hidden content from layout or preserve its size with
maintainSize.
Media
Image and VideoPlayer provide responsive media frames while keeping the
native HTML props and events available:
import { Image, VideoPlayer } from "@digitwhale_innovations/uhuru-ui";
<Image
alt="Workspace preview"
aspectRatio={16 / 9}
fit="cover"
src="/workspace-preview.jpg"
fallback={<EmptyState title="Preview unavailable" />}
/>
<VideoPlayer
controls
fit="contain"
poster="/workspace-poster.jpg"
sources={[
{ src: "/workspace.webm", type: "video/webm" },
{ src: "/workspace.mp4", type: "video/mp4" },
]}
/>;Both components support responsive sizing, aspect ratios, object-fit modes, fallback content, and standard browser loading/error behavior.
Text inputs, SearchInput, TextArea, and Select accept prefixIcon and
suffixIcon React nodes. The package uses Phosphor for its internal icons,
while consumers can provide any compatible icon node:
import { MagnifyingGlass, User } from "@phosphor-icons/react";
<TextField prefixIcon={<User />} suffixIcon={<MagnifyingGlass />} />
<Select prefixIcon={<User />} options={[{ label: "Admin", value: "admin" }]} />SearchInput can render a lightweight result surface with searchDropArea.
Pass result objects for the default keyboard-accessible list, a React node for
fixed custom content, or a render function for query-aware custom content. The
surface uses FloatingPortal, stays inside the viewport, and renders above
card and panel stacking contexts.
<SearchInput
value={query}
onChange={(event) => setQuery(event.target.value)}
searchDropArea={[
{ id: "button", label: "Button", description: "Action component" },
{ id: "layout", label: "UhuruAppLayout", description: "Application shell" },
]}
onSearchResultSelect={openResult}
/>Input and TextField pass through native HTML input types such as email,
password, number, date, url, tel, search, time, and file-related
types. Uhuru also provides number-with-options, which renders a numeric
input with a native datalist:
<Input
label="Budget"
type="number-with-options"
options={[
{ label: "Starter", value: "100" },
{ label: "Team", value: "500" },
]}
prefixIcon={<CurrencyDollar />}
suffixIcon={<Calculator />}
/>For per-instance custom rows, provide renderItem. The panel still owns
selection, active state, nesting, and accessibility:
<UhuruAppLayout
leftPanel={
<UhuruGroupedPanel
activeId="overview"
onItemSelect={(item) => navigate(`/workspace/${item.id}`)}
renderItem={(item, { active }) => (
<>
<span className={active ? "font-semibold" : ""}>{item.label}</span>
{item.badge ? <span>{item.badge}</span> : null}
</>
)}
groups={[
{
id: "workspace",
label: "Workspace",
items: [{ id: "overview", label: "Overview", badge: "New" }],
},
]}
/>
}
>
<main>{/* page content */}</main>
</UhuruAppLayout>;Use the rightPanel slot for right-side content. No hook is required because
the layout slot determines where the panel renders, while UhuruPanel owns the
panel frame, scrolling, header, and footer.
Alerts
Use useUhuruAlert() to trigger app-level alerts from any component inside UhuruProvider.
import { Button, useUhuruAlert } from "@digitwhale_innovations/uhuru-ui";
export function SaveButton() {
const { alert } = useUhuruAlert();
return (
<Button
onClick={() =>
alert({
title: "Saved",
description: "Your changes were applied successfully.",
tone: "success",
dismissible: true,
})
}
>
Save changes
</Button>
);
}You can also configure the alert system at the provider level:
import { UhuruProvider } from "@digitwhale_innovations/uhuru-ui";
<UhuruProvider
alertConfig={{
dismissible: false,
position: "top-right",
minWidth: 280,
maxWidth: 420,
offset: 24,
gap: 12,
duration: 5000,
stackLimit: 3,
zIndex: 70,
}}
>
<App />
</UhuruProvider>Command alerts use the same Alert component and stylesheet as inline alerts.
The trailing X button is optional and is hidden by default. Enable it for one
alert with dismissible: true, or set alertConfig.dismissible to true to
enable it for all alerts from that provider.
For inline alerts, use the same prop directly:
<Alert dismissible onDismiss={() => setVisible(false)} title="Heads up">
System updates are available.
</Alert>React Compatibility
Supported React peer range:
- React 17
- React 18
- React 19
Available Component Areas
Actions
ButtonIconButtonLinkButtonSplitButtonMenuDropdownMenuUhuruSignInWithDigitwhale
Inputs
TextFieldTextAreaSearchInputSelectComboboxPasswordInputOtpInputDatePickerTimePickerFileUploadAIInputFormFieldNumberInput
AIInput supports a full composer and a compact launch/search treatment. Use
variant="compact" with up to two preTrailingActions and one
mainTrailingAction; all actions are typed descriptors with a label, optional
leading icon, disabled state, and callback. File picking and submit payloads
are shared with the full composer.
Selection
CheckboxRadioSwitchSegmentedControlSlider
Feedback
AlertEmptyStateEmptyStateScreenNotFoundScreenSkeletonSpinnerTooltipProgressBar
Navigation
BreadcrumbsPaginationSidebarStepperTabsTopNav
Data Display
TableDataGridListTimelineStatCard
Table and DataGrid accept optional title and subtitle content so a
dataset can explain itself without a separate wrapper:
<Table
title="Recent orders"
subtitle="Orders received across all storefronts."
leadingIcon={<ClipboardText />}
showPagination
currentPage={page}
itemCount={124}
columns={columns}
data={rows}
onSortChange={setSort}
/>
<DataGrid
title="Workspace entries"
subtitle="Searchable and paginated records."
leadingIcon={<ChartBar />}
showPagination={false}
columns={columns}
data={rows}
currentPage={page}
itemCount={124}
/>Overlay
ModalDrawerPopoverConfirmDialogCommandPaletteToastuseOverlay
Layout
StackExpandedFlexibleSpacerSizedBoxWrapAspectRatioVisibilityPositionedGridCenterFormRowColumnPaddingMarginContainerNavbarPanelUhuruPanelDividerPageHeader
Media
ImageVideoPlayer
Typography and Status
HeadingTextLabelCaptionCodeLinkMonoDataTextBadgeTagAvatarPresenceIndicator
Avatar supports initials, image URLs, custom assets, and an optional editable
file-picker mode:
<Avatar
accept=".png,.jpg,.jpeg,.webp"
editable
initials="AL"
maxFileSize={5 * 1024 * 1024}
name="Ada Lovelace"
onEdit={(file) => uploadAvatar(file)}
onEditError={({ message }) => showError(message)}
src={profile.avatarUrl}
/>App Shell and Foundations
UhuruAppLayoutUhuruSectionHeadingtokenscnshowCodeuseUhuruCode
Example Usage
import {
Badge,
Button,
Card,
CardBody,
CardHeader,
Modal,
useOverlay,
UhuruProvider,
} from "@digitwhale_innovations/uhuru-ui";
export function WorkspacePanel() {
const modal = useOverlay();
return (
<UhuruProvider theme="light" density="compact">
<Card>
<CardHeader eyebrow="Workspace" title="Uhuru Billing" description="Current production workspace." />
<CardBody className="flex flex-wrap gap-3">
<Badge tone="accent">Live</Badge>
<Button onClick={modal.open}>Open modal</Button>
</CardBody>
</Card>
<Modal isOpen={modal.isOpen} title="Review changes">
Content goes here.
</Modal>
</UhuruProvider>
);
}Notes For External Consumers
- This package is published and can be installed in a new React app like any other dependency.
- For the smoothest setup, use a modern toolchain such as Vite.
- If your bundler does not transpile TypeScript or ESM from dependencies, you may need to adjust that configuration.
- Do not rely on the local workspace source paths inside this repository when consuming the published package externally.
More Setup Help
For a step-by-step external React setup, see REACT_SETUP.md.
Repository
Source and issues: Whales-Group/digitwhale_uhuru_ui_kit
Authentication Pages
Uhuru provides auth UI as reusable pages, not as a backend or session manager.
Use AuthFlow when the application wants one complete coordinated flow, or use
the individual pages when each screen has its own route and loader state.
import {
AuthFlow,
CreateAccountPage,
ForgotPasswordPage,
LoginPage,
OtpVerificationPage,
PasskeyPage,
} from "@digitwhale_innovations/uhuru-ui";
<AuthFlow
initialPage="login"
onForgotPassword={(email) => api.requestPasswordReset(email)}
onOtpSubmit={(code) => api.verifyOtp(code)}
onPasskey={() => api.signInWithPasskey()}
onCreateAccount={(data) => api.createAccount(data)}
/>
<LoginPage
onSubmit={({ email, password }) => api.signIn(email, password)}
onForgotPassword={(email) => navigate(`/forgot-password?email=${email}`)}
onCreateAccount={() => navigate("/create-account")}
onPasskey={() => navigate("/passkey")}
/>The standalone pages are LoginPage, ForgotPasswordPage,
OtpVerificationPage, PasskeyPage, and CreateAccountPage. The account page
is stepped automatically: name, email/password, then date of birth and phone.
Its onSubmit callback receives { firstName, lastName, email, password,
dateOfBirth, phone }. Auth components intentionally leave navigation, API
requests, WebAuthn, rate limiting, tokens, and session storage to the consumer.
The coordinator is configurable end to end:
<AuthFlow
initialPage="otp"
frame={{ animation: "slide-up", variant: "quiet" }}
otp={{ channel: "your phone", length: 6, loading: isVerifying, error: verifyError }}
passkey={{ loading: isStartingPasskey, error: passkeyError }}
createAccount={{ defaultValues: { email: inviteEmail } }}
onScreenChange={setAuthScreen}
onLoginSubmit={() => "two-factor"}
renderPage={(screen, navigation) =>
screen === "login"
? <CustomLogin onCreateAccount={() => navigation.go("create-account")} />
: undefined
}
/>initialPage selects login, forgot-password, otp, passkey, or
create-account. initialScreen is a compatibility alias. Per-page objects
accept the standalone page props. renderPage replaces a built-in screen and
receives screen, go, and back. Custom auth pages should use Uhuru
TextField, PasswordInput, and OtpInput to preserve labels, icons, focus
states, obscuring, validation, and theme behavior.
TwoFactorPage adds authenticator, email-otp, and recovery-key methods.
Pass availableMethods to control which methods are shown, defaultMethod or
controlled method to select one, and handle onSubmit({ method, value }).
animation supports none, fade, slide-up, and scale; variant supports
default, minimal, and quiet. error, loading, onBack, onResend,
onMethodChange, and submit callbacks are available for the relevant view.
onLoginSubmit and onRecoverySubmit receive the submitted value plus flow
navigation and may return "two-factor" or another auth screen to transition
seamlessly. onTwoFactorSubmit supports the same transition pattern after
verification.
AuthFlow is route-aware. By default it exposes the focused login and otp
views at /login and /verify-code. Configure a larger flow explicitly:
<AuthFlow
views={["login", "otp", "two-factor"]}
routes={{ login: "/login", otp: "/verify-code", "two-factor": "/2fa" }}
onLoginSubmit={() => "otp"}
onTwoFactorSubmit={verifyTwoFactor}
onRouteChange={(path) => router.navigate(path)}
currentPath={location.pathname}
/>Every screen has a routable path. If the current path is unknown, or maps to a
screen not listed in views, Uhuru renders an explicit route error instead of
silently showing an unrelated page. Use enabledViews as an alias for
views.
