npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@digitwhale_innovations/uhuru-ui

v0.2.61

Published

Digitwhale Innovations' source-first React component library for Uhuru products.

Downloads

11,118

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-ui

If 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

  • UhuruProvider controls theme, density, radius, preset accent selection, and shared app tokens.
  • UhuruProvider can also configure the built-in alert system globally.
  • Use presetAccent to switch between the bundled accent presets or a custom preset key.
  • Use accentPresets to add or replace preset options without rebuilding component styles.
  • Use colors when 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, and amber.

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

  • Button
  • IconButton
  • LinkButton
  • SplitButton
  • Menu
  • DropdownMenu
  • UhuruSignInWithDigitwhale

Inputs

  • TextField
  • TextArea
  • SearchInput
  • Select
  • Combobox
  • PasswordInput
  • OtpInput
  • DatePicker
  • TimePicker
  • FileUpload
  • AIInput
  • FormField
  • NumberInput

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

  • Checkbox
  • Radio
  • Switch
  • SegmentedControl
  • Slider

Feedback

  • Alert
  • EmptyState
  • EmptyStateScreen
  • NotFoundScreen
  • Skeleton
  • Spinner
  • Tooltip
  • ProgressBar

Navigation

  • Breadcrumbs
  • Pagination
  • Sidebar
  • Stepper
  • Tabs
  • TopNav

Data Display

  • Table
  • DataGrid
  • List
  • Timeline
  • StatCard

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

  • Modal
  • Drawer
  • Popover
  • ConfirmDialog
  • CommandPalette
  • Toast
  • useOverlay

Layout

  • Stack
  • Expanded
  • Flexible
  • Spacer
  • SizedBox
  • Wrap
  • AspectRatio
  • Visibility
  • Positioned
  • Grid
  • Center
  • Form
  • Row
  • Column
  • Padding
  • Margin
  • Container
  • Navbar
  • Panel
  • UhuruPanel
  • Divider
  • PageHeader

Media

  • Image
  • VideoPlayer

Typography and Status

  • Heading
  • Text
  • Label
  • Caption
  • Code
  • Link
  • MonoDataText
  • Badge
  • Tag
  • Avatar
  • PresenceIndicator

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

  • UhuruAppLayout
  • UhuruSectionHeading
  • tokens
  • cn
  • showCode
  • useUhuruCode

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.