@wocha/ui
v0.1.0
Published
Themed, customisable UI components for Wocha authentication
Maintainers
Readme
@wocha/ui
Themed, customisable React components for Wocha authentication. Works with both @wocha/react (SPAs) and @wocha/nextjs (App Router).
Install
npm install @wocha/ui @wocha/react
# or
npm install @wocha/ui @wocha/nextjsImport the stylesheet once in your app:
import "@wocha/ui/styles.css";Quick start
React SPA
import { WochaProvider } from "@wocha/react";
import { WochaThemeProvider, SignIn, UserButton } from "@wocha/ui";
import "@wocha/ui/styles.css";
export function App() {
return (
<WochaProvider config={greetConfig}>
<WochaThemeProvider>
<header>
<UserButton profileUrl="/profile" organisationUrl="/settings/org" />
</header>
<main>
<SignIn redirectUrl="/dashboard" />
</main>
</WochaThemeProvider>
</WochaProvider>
);
}Next.js App Router
// app/layout.tsx
import { WochaSessionProvider } from "@wocha/nextjs/client";
import { WochaThemeProvider } from "@wocha/ui";
import "@wocha/ui/styles.css";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<WochaSessionProvider apiUrl={process.env.WOCHA_API_URL}>
<WochaThemeProvider sdk="nextjs" apiUrl={process.env.WOCHA_API_URL}>
{children}
</WochaThemeProvider>
</WochaSessionProvider>
</body>
</html>
);
}Components
<SignIn>
A styled sign-in form that redirects to Wocha hosted login.
import { SignIn } from "@wocha/ui";
<SignIn
redirectUrl="/dashboard"
organisation="org_abc123"
providers={["google", "github"]}
appearance={{
variables: { "--wocha-color-primary": "#6366f1" },
}}
/>Props
| Prop | Type | Description |
|------|------|-------------|
| redirectUrl | string | URL to return to after sign-in |
| organisation | string | Organisation ID hint for hosted login |
| providers | SocialProvider[] | Social buttons to show (default: all four) |
| appearance | WochaAppearance | Per-component theme overrides |
| withCard | boolean | Wrap in AuthCard (default: true) |
| title | ReactNode | Card heading |
| description | ReactNode | Card subheading |
| buttonLabel | string | Primary button text |
Individual import:
import { SignIn } from "@wocha/ui/sign-in";<SignUp>
Registration flow — same API as <SignIn> but redirects to hosted registration.
import { SignUp } from "@wocha/ui";
<SignUp
redirectUrl="/welcome"
providers={["google", "apple"]}
title="Join Acme Corp"
description="Create an account to get started."
/><UserButton>
Dropdown showing the current user's avatar, name, and email.
import { UserButton } from "@wocha/ui";
// Full variant (default) — avatar, name, email, chevron
<UserButton
variant="full"
profileUrl="/profile"
organisationUrl="/settings/organisation"
/>
// Compact — avatar only
<UserButton variant="compact" />Menu items: Profile, Organisation, Sign out. Supports keyboard navigation (Arrow keys, Enter, Escape) and ARIA menu semantics.
<OrgSwitcher>
Organisation switcher dropdown for multi-org users.
import { OrgSwitcher } from "@wocha/ui";
<OrgSwitcher
getOrgLabel={(id) => orgNames[id] ?? id}
createOrganisationUrl="/organisations/new"
showSingle
/>Shows a checkmark on the active organisation. Includes a Create organisation action when createOrganisationUrl or onCreateOrganisation is provided.
<UserProfile>
Full-page profile management with sections for profile details, security, sessions, and organisations. Uses the Management API with the user's OAuth access token — requires apiUrl on your provider.
import { UserProfile } from "@wocha/ui";
<UserProfile
sections={["profile", "security", "sessions", "connections", "organisations"]}
onSave={(data) => console.log("Saved", data)}
appearance={{ theme: "light" }}
/>Sections
| Section | Features |
|---------|----------|
| profile | Display name, avatar placeholder, read-only email, metadata display |
| security | Password change form, passkey/MFA placeholders |
| sessions | Active sessions with device, IP, last active, revoke button |
| connections | Linked social accounts with connect/disconnect actions |
| organisations | Orgs the user belongs to with role badges |
Props
| Prop | Type | Description |
|------|------|-------------|
| sections | ProfileSection[] | Sections to render (default: all four) |
| activeSection | ProfileSection | Controlled active section |
| onSectionChange | (section) => void | Called when section changes |
| onSave | (data) => void | Called after profile save succeeds |
| appearance | WochaAppearance | Per-component theme overrides |
| hideNavigation | boolean | Hide internal tabs (use with UserPortal) |
Individual import:
import { UserProfile, useProfile } from "@wocha/ui/user-profile";<UserPortal>
Standalone full-page self-service portal with header, sidebar navigation, and sign-out button. Mount at a dedicated route (e.g. /account).
// app/account/page.tsx (Next.js)
import { UserPortal } from "@wocha/ui";
export default function AccountPage() {
return (
<UserPortal
title="My account"
sections={["profile", "security", "sessions", "connections", "organisations"]}
/>
);
}Link from <UserButton profileUrl="/account" /> to connect the dropdown to the portal.
Props
| Prop | Type | Description |
|------|------|-------------|
| title | string | Portal heading (default: "Account settings") |
| logo | ReactNode | Custom logo in header |
| sections | ProfileSection[] | Sidebar sections to show |
| appearance | WochaAppearance | Theme overrides |
useProfile()
Hook for programmatic profile management. Returns user data, mutations, and loading state.
import { useProfile } from "@wocha/ui";
const {
user,
updateProfile,
changePassword,
sessions,
revokeSession,
organisations,
isLoading,
error,
refresh,
} = useProfile();
await updateProfile({ name: "Alice Smith" });
await changePassword("old-pass", "new-pass");
await revokeSession("ses_abc123");Requires apiUrl on WochaProvider (React) or WochaSessionProvider (Next.js).
<Protect>
Conditional rendering based on authentication, permissions, or roles.
import { Protect } from "@wocha/ui";
// Auth-only gate
<Protect fallback={<p>Please sign in.</p>}>
<Dashboard />
</Protect>
// Role gate
<Protect role="admin" fallback={<p>Admins only.</p>}>
<AdminPanel />
</Protect>
// Permission gate (SpiceDB)
<Protect
permission={{
resource: { type: "document", id: "doc_123" },
permission: "edit",
}}
fallback={<p>You cannot edit this document.</p>}
>
<EditForm />
</Protect>Combine role and permission — both must pass.
<AuthCard>
Styled card wrapper for auth UI. Used internally by <SignIn> and <SignUp> but available standalone.
import { AuthCard, WochaLogo } from "@wocha/ui";
<AuthCard
logo={<WochaLogo className="wocha-card__logo-icon" />}
title="Reset password"
description="Enter your email to receive a reset link."
footer={<a href="/sign-in">Back to sign in</a>}
>
{/* custom form content */}
</AuthCard>Theming
All styles use CSS custom properties scoped with the .wocha- prefix to avoid conflicts with your app.
Default theme
Dark background (#1c1917), gold accent (#D4AF37), clean system typography.
Light mode
Set data-theme="light" on the theme root, or use the provider:
<WochaThemeProvider appearance={{ theme: "light" }}>
{children}
</WochaThemeProvider>System preference:
<WochaThemeProvider appearance={{ theme: "system" }}>
{children}
</WochaThemeProvider>Custom variables
Override any CSS variable globally or per component:
<WochaThemeProvider
appearance={{
theme: "dark",
radius: "lg",
fontSize: "md",
variables: {
"--wocha-color-primary": "#818cf8",
"--wocha-color-background": "#0f0f0f",
"--wocha-font-family": "'Inter', sans-serif",
},
}}
>
{children}
</WochaThemeProvider>Available variables:
| Variable | Purpose |
|----------|---------|
| --wocha-color-background | Page background |
| --wocha-color-foreground | Primary text |
| --wocha-color-card | Card / dropdown background |
| --wocha-color-primary | Buttons, avatar, accents |
| --wocha-color-muted-foreground | Secondary text |
| --wocha-color-border | Borders |
| --wocha-color-destructive | Sign out, errors |
| --wocha-radius | Border radius scale |
| --wocha-font-size-base | Base font size |
| --wocha-spacing-* | xs, sm, md, lg, xl |
| --wocha-shadow-* | sm, md, lg |
Raw CSS
Import and extend the stylesheet directly:
@import "@wocha/ui/styles.css";
.wocha-root[data-theme="light"] {
--wocha-color-primary: #7c3aed;
}Layout example
A complete unauthenticated / authenticated layout:
import {
WochaThemeProvider,
SignIn,
SignUp,
UserButton,
OrgSwitcher,
Protect,
useWochaUiAuth,
} from "@wocha/ui";
function Header() {
return (
<header style={{ display: "flex", gap: "1rem", alignItems: "center" }}>
<OrgSwitcher getOrgLabel={(id) => id.replace("org_", "")} />
<UserButton variant="full" profileUrl="/profile" />
</header>
);
}
function HomePage() {
const { isAuthenticated, isLoading } = useWochaUiAuth();
if (isLoading) return <p>Loading…</p>;
if (!isAuthenticated) {
return (
<div style={{ display: "grid", gap: "2rem", maxWidth: "24rem", margin: "4rem auto" }}>
<SignIn redirectUrl="/dashboard" />
<SignUp redirectUrl="/welcome" />
</div>
);
}
return (
<>
<Header />
<Protect
permission={{ resource: { type: "app", id: "main" }, permission: "view" }}
fallback={<p>Access denied.</p>}
>
<main>Protected content</main>
</Protect>
</>
);
}
export function AppShell() {
return (
<WochaThemeProvider appearance={{ theme: "dark" }}>
<HomePage />
</WochaThemeProvider>
);
}Note:
useWochaUiAuthexposes the unified auth bridge used internally by UI components. For general app logic, preferuseSession/useWochaAuthfrom your SDK.
Individual imports
Tree-shake by importing components directly:
import { SignIn } from "@wocha/ui/sign-in";
import { UserButton } from "@wocha/ui/user-button";
import { Protect } from "@wocha/ui/protect";
import { AuthCard } from "@wocha/ui/auth-card";
import { OrgSwitcher } from "@wocha/ui/org-switcher";
import { SignUp } from "@wocha/ui/sign-up";
import { UserProfile, UserPortal, useProfile } from "@wocha/ui/user-profile";Accessibility
- All interactive elements have visible focus rings (
:focus-visible) - Dropdowns use
role="menu"/role="listbox"with keyboard navigation - Social buttons include
aria-labelwith provider name - Avatar initials served as fallback when no profile image is available
- Separator elements use
role="separator"
Requirements
- React 18+
- One of
@wocha/reactor@wocha/nextjs(peer dependency) - No CSS framework required — pure CSS custom properties
License
Apache-2.0
Enterprise widgets
Embeddable React components for enterprise self-service — SSO, SCIM, and domain verification. Each widget works standalone with a portal token generated from the Wocha Console.
npm install @wocha/uiimport "@wocha/ui/styles.css";
import { EnterpriseSetup, SSOSetup, SCIMSetup, DomainVerification } from "@wocha/ui/enterprise";
export function CustomerOnboarding({ portalToken }: { portalToken: string }) {
return (
<EnterpriseSetup
portalToken={portalToken}
portalBaseUrl="https://console.wocha.ai"
appearance={{ theme: "light" }}
onComplete={() => console.log("Enterprise setup complete")}
/>
);
}<EnterpriseSetup>
Combined tabbed widget with SSO, SCIM, and domain verification plus an overall progress indicator.
| Prop | Type | Description |
|------|------|-------------|
| portalToken | string | Portal token from the Wocha Console |
| portalBaseUrl | string | Console origin hosting portal APIs (default: https://console.wocha.ai) |
| onComplete | () => void | Called when a section completes successfully |
| appearance | WochaAppearance | Theme overrides (same CSS variables as auth components) |
| className | string | Additional root class name |
<SSOSetup>
Multi-step SAML/OIDC wizard with domain verification and connection testing.
<SSOSetup
portalToken={token}
onComplete={() => router.push("/done")}
appearance={{
variables: { "--wocha-color-primary": "#2563eb" },
}}
/><SCIMSetup>
Shows SCIM endpoint and bearer token, IdP-specific setup guides, and connection testing.
<SCIMSetup portalToken={token} onComplete={handleDone} /><DomainVerification>
Add domains, display DNS TXT records, and verify ownership.
<DomainVerification
portalToken={token}
onVerified={(domain) => console.log(`${domain} verified`)}
/>Standalone usage
Each widget wraps its own portal provider and calls the Console portal API internally:
GET /api/portal/validate?token=…POST /api/portal/setupwithAuthorization: Bearer {portalToken}
Point portalBaseUrl at your Wocha Console deployment when self-hosting.
Theming
Enterprise widgets use the same .wocha- CSS custom properties as auth components. Wrap in WochaThemeProvider or pass appearance directly to each widget.
Accessibility
- Step indicators and tab navigation use semantic lists and
aria-selected - Loading states expose
role="status" - Errors use
role="alert" - All controls are keyboard accessible with visible focus rings
