@ozura/ui
v0.9.0
Published
Shared UI components and design tokens for Ozura frontends
Downloads
2,065
Readme
@ozura/ui
The shared design system for all Ozura frontends — Dashboard, Vault, and Developer Tools.
npm: @ozura/ui | GitHub: Ozura-Inc/ozura-ui
Why @ozura/ui exists
Ozura has 3 customer-facing frontends that must look and feel like one product:
| App | Repo | Port | Purpose |
|-----|------|------|---------|
| Dashboard | ozurapay-frontend-v2 | 3000 | Merchant/ISO/ISV management |
| Vault | token-vault-frontend | 3001 | PCI token storage |
| Developer Tools | PayAPI-FE | 3002 | API sandbox, logs, webhooks |
@ozura/ui is the single source of truth for:
- Design tokens (colors, fonts, spacing) via
globals.css - Shared components (sidebar, header, toasts)
- Consistent behavior (theme toggle, cross-app theme sync)
If a UI element appears in more than one frontend, it belongs in @ozura/ui.
Installation
npm install @ozura/ui sonnerSetup
1. Import the design tokens
In your app's globals.css, import the base tokens before Tailwind:
@import "@ozura/ui/globals.css";
@import "tailwindcss";
/* App-specific overrides and styles go below */This gives you all the Ozura design tokens (colors, sidebar, status, radius, etc.) for both light and dark themes, plus the Tailwind v4 @theme inline mappings.
You can add app-specific overrides after the imports. The base tokens are inherited — only override what's different for your app.
2. Root layout
import type { Metadata } from "next";
import { Rubik, Inter, JetBrains_Mono } from "next/font/google";
import { ThemeProvider } from "next-themes";
import { OzuraToaster } from "@ozura/ui";
import "./globals.css";
const rubik = Rubik({ subsets: ["latin"], variable: "--font-rubik", weight: ["300","400","500","600","700"] });
const inter = Inter({ subsets: ["latin"], variable: "--font-inter", weight: ["400","500","600"] });
const jetbrainsMono = JetBrains_Mono({ subsets: ["latin"], variable: "--font-jetbrains-mono", weight: ["400","500"] });
export const metadata: Metadata = {
icons: { icon: "/icon.svg" },
title: { default: "Ozura App", template: "%s — Ozura" },
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`${rubik.variable} ${inter.variable} ${jetbrainsMono.variable} font-rubik`}>
<ThemeProvider attribute="class" defaultTheme="light" enableSystem={false}>
{children}
<OzuraToaster />
</ThemeProvider>
</body>
</html>
);
}3. Layout structure
┌─────────────────────────────────────────────────┐
│ OzuraHeader (fixed, top, left: 260px) │
│ [leftContent] [theme] [profile ▾] │
├──────────┬──────────────────────────────────────┤
│ Ozura │ Content Area (px-8 py-6) │
│ Sidebar │ pt-16 offset for header │
│ 260px │ │
│ fixed │ │
└──────────┴──────────────────────────────────────┘Components
OzuraShell (Recommended)
The all-in-one layout component. Handles sidebar, header, mobile responsiveness, and content padding. This is the recommended way to use @ozura/ui in any app.
import { OzuraShell } from '@ozura/ui';
import type { NavItem } from '@ozura/ui';
import { LayoutDashboard, KeyRound, Users } from 'lucide-react';
const navItems: NavItem[] = [
{ label: 'Dashboard', href: '/', icon: LayoutDashboard },
{ label: 'Tokens', href: '/tokens', icon: KeyRound },
{ label: 'Members', href: '/members', icon: Users },
];
export default function AppLayout({ children }) {
return (
<OzuraShell
navItems={navItems}
user={{ name: 'Max', email: '[email protected]' }}
onLogout={() => { window.location.href = '/login'; }}
leftContent={<SearchBar />}
settingsHref="/settings"
>
{children}
</OzuraShell>
);
}What OzuraShell handles
| Feature | Desktop (≥768px) | Mobile (<768px) |
|---------|-----------------|-----------------|
| Sidebar | Fixed left, 260px | Hidden, opens as overlay via hamburger |
| Header | Fixed top, profile + theme toggle | Compact with hamburger + logo + avatar |
| Content | padding: 24px 32px, offset for sidebar/header | padding: 16px, offset for mobile header |
| Navigation | Click navigates normally | Click navigates + closes sidebar overlay |
| Body scroll | Normal | Locked when sidebar overlay is open |
OzuraShell Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| navItems | NavItem[] | required | Sidebar navigation items |
| user | { name, email, avatarUrl? } | — | Profile dropdown in header |
| onLogout | () => void | — | Logout handler |
| leftContent | ReactNode | — | Header left slot (search, project selector) |
| showThemeToggle | boolean | true | Show sun/moon theme toggle |
| settingsHref | string | /settings | Settings route in sidebar |
| backLink | { label, href } | — | Back link in sidebar (e.g. "← Dashboard") |
| docsUrl | string | https://docs.ozura.com | Documentation link |
| darkLogoSrc | string | /logos/ozura-logo-dark.svg | Logo for light theme |
| lightLogoSrc | string | /logos/ozura-logo-white.svg | Logo for dark theme |
| children | ReactNode | required | Page content |
OzuraSidebar
import { OzuraSidebar } from '@ozura/ui';
import type { NavItem } from '@ozura/ui';
import { LayoutDashboard, KeyRound } from 'lucide-react';
const navItems: NavItem[] = [
{ label: 'Dashboard', href: '/', icon: LayoutDashboard },
{ label: 'Tokens', href: '/tokens', icon: KeyRound },
];
<OzuraSidebar navItems={navItems} settingsHref="/settings" />| Prop | Type | Default | Description |
|------|------|---------|-------------|
| navItems | NavItem[] | required | Nav items |
| backLink | { label, href } | — | Back link (e.g. DevTools → Dashboard) |
| docsUrl | string | https://docs.ozura.com | Docs link |
| settingsHref | string | /settings | Settings route |
| darkLogoSrc | string | /logos/ozura-logo-dark.svg | Logo for light theme |
| lightLogoSrc | string | /logos/ozura-logo-white.svg | Logo for dark theme |
OzuraHeader
import { OzuraHeader } from '@ozura/ui';
<OzuraHeader
user={{ name: 'Max', email: '[email protected]' }}
onLogout={() => { window.location.href = '/login'; }}
showThemeToggle={true}
leftContent={<YourSearchBar />}
/>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| user | { name, email, avatarUrl? } | — | Profile dropdown info |
| onLogout | () => void | — | Logout handler |
| leftContent | ReactNode | — | Left slot (search, project selector) |
| showThemeToggle | boolean | true | Show sun/moon toggle |
| sidebarWidth | number | 260 | Header left offset |
Toasts (Sonner)
All apps use Sonner for notifications via ozToast:
import { ozToast } from '@ozura/ui';
// Success
ozToast.success('Payment processed');
// Error with description
ozToast.error('Transaction failed', 'Insufficient funds');
// Warning
ozToast.warning('Rate limit approaching');
// Loading → resolves
ozToast.promise(fetchData(), {
loading: 'Processing...',
success: 'Done!',
error: 'Failed',
});Setup: Add <OzuraToaster /> once in your root layout (see Setup section above).
Pattern: Never use alert(), console.log() for user-facing messages, or custom toast implementations. Always use ozToast.
Design Standards
| Standard | Value |
|----------|-------|
| Sidebar width | 260px |
| Header height | 64px (h-16) |
| Content padding | px-8 py-6 |
| Fonts | Rubik (primary), Inter (data/body), JetBrains Mono (code) |
| CSS format | Hex values with @theme inline (Tailwind v4) |
| Theme storage | localStorage key "theme", values "light" / "dark" |
| Dark mode | class="dark" on <html> via next-themes |
| Mobile breakpoint | 768px |
| Border radius | --radius: 0.5rem, --radius-button: 0.375rem |
| Toast library | Sonner via ozToast |
| Title pattern | { default: "App Name", template: "%s — Ozura" } |
Required Assets
Copy public/logos/ from this repo to your app's public/ directory:
public/logos/ozura-logo-dark.svgpublic/logos/ozura-logo-white.svgpublic/icon.svg— favicon (place insrc/app/icon.svg)public/favicon.svg— fallback favicon
Making Changes
To shared components (sidebar, header, toasts)
- Clone:
git clone [email protected]:Ozura-Inc/ozura-ui.git - Edit in
src/components/ - Test locally:
npm run build && npm link→npm link @ozura/uiin your app - Bump version in
package.json - Publish:
npm publish --access public - Update all 3 apps:
npm install @ozura/ui@<version>
To design tokens (colors, spacing)
- Edit
src/globals.cssin this repo - Publish new version
- All apps inherit the changes automatically (they import
@ozura/ui/globals.css)
To app-specific styles
Edit the app's own globals.css — add overrides AFTER the imports. Don't duplicate tokens that exist in @ozura/ui/globals.css.
Rule: If it should look the same everywhere → put it in @ozura/ui. If it's app-specific → keep it in the app.
Development
npm install
npm run build # Build with tsup
npm run dev # Watch mode
npm run lint # Type check