Auth
Sign-up screen
A premium registration screen: an original dark, textured canvas carries the pitch and feature list as plain text, while a real form floats on top in its own light card — Google/GitHub/SSO buttons, name/username/email/password with a helper hint, and Form validating the whole set on submit.
How it's built
oks-ui's dark theme only activates via `data-theme="dark"` on the document root (`:root[data-theme="dark"]` in its tokens CSS) — not on an arbitrary nested `<div>`. Real FormFieldSet/Button components dropped directly onto a custom dark background with no theme flip render dark-on-dark, illegible text.
The fix here: the dark canvas carries only plain text we colour ourselves (the pitch, the feature list); the actual form lives in a normal light `bg-paper-raised` card floating on top, so every oks-ui component renders with the contrast it was built for — no fighting the theme system.
The brand mark and the three feature icons are still real `Avatar` components even on the dark canvas — they're tinted via the `classNames` slot override (`{ base: "bg-white/10", icon: "text-white" }`) instead of the `color` prop, since `color` resolves through the same `:root[data-theme]`-scoped tokens that don't apply here.
The headline is a real `PageTitle` too — `classNames={{ base: "flex-col items-start" }}` because `PageTitle`'s own wrapper is `display: inline-flex` with no set direction, built for a compact inline icon+title rather than a stacked heading.
`description="Minimum length is 8 characters."` on the password FormFieldSet renders as real helper text under the field, not a separate paragraph you have to position yourself.
The three SSO buttons are `Button variant="bordered"` in a `grid-cols-3` row — Google's mark is the real multi-colour icon; the other two are simple, original single-colour marks, not reproductions of a specific brand's artwork.
"Terms of Service" and "Log in" are real `Button variant="link"` components (with `href`, no `type`, so it renders as an `<a>`), not raw anchors — `no-underline` overrides the variant's default underline to match this pattern's plain-text link style.
Layout stacks by default and only moves to a side-by-side canvas + card at `@2xl` — on a narrow screen the pitch sits above the form instead of disappearing, since a first-time visitor benefits from the context more than a returning one does.
Source
One file · copy & pasteimport { Avatar } from "oks-ui/avatar";
import { Button } from "oks-ui/button";
import { Form, FormFieldSet } from "oks-ui/form-field-set";
import { PageTitle } from "oks-ui/page-title";
import { CheckIcon, PieChartIcon, ShieldIcon, UsersIcon } from "../../showcase/icons";
// Standard, single-colour SSO service marks — the same idiom every
// "continue with X" button on the web uses, not reproducing any artwork.
function GoogleIcon() {
return (
<svg width="16" height="16" viewBox="0 0 18 18" aria-hidden="true">
<path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84c-.21 1.13-.85 2.09-1.81 2.73v2.26h2.92c1.71-1.57 2.69-3.88 2.69-6.63z" />
<path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.81.54-1.84.87-3.04.87-2.34 0-4.32-1.58-5.03-3.71H.96v2.33C2.44 15.98 5.48 18 9 18z" />
<path fill="#FBBC05" d="M3.97 10.72c-.18-.54-.28-1.12-.28-1.72s.1-1.18.28-1.72V4.95H.96A8.99 8.99 0 0 0 0 9c0 1.45.35 2.83.96 4.05l3.01-2.33z" />
<path fill="#EA4335" d="M9 3.58c1.32 0 2.51.45 3.44 1.35l2.59-2.59C13.46.89 11.43 0 9 0 5.48 0 2.44 2.02.96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z" />
</svg>
);
}
function GitHubIcon() {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z" />
</svg>
);
}
// A simplified, original geometric mark (not a reproduction of any specific
// brand's logo artwork) standing in for a third SSO provider slot.
function ThirdPartyIcon() {
return (
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
<path d="M8 1.2 14.4 6l-2.4 7.3H4L1.6 6 8 1.2Z" opacity="0.85" />
</svg>
);
}
const features = [
{
icon: <UsersIcon size={18} />,
title: "Invite your whole team",
text: "No per-seat pricing games — bring everyone in from day one.",
},
{
icon: <PieChartIcon size={18} />,
title: "Real-time usage insights",
text: "See exactly what's being used, by whom, as it happens.",
},
{
icon: <ShieldIcon size={18} />,
title: "Enterprise-grade security",
text: "SSO, audit logs, and encryption at rest — on every plan.",
},
];
export function SignUp() {
return (
<div className="@container relative w-full overflow-hidden bg-[#04140a]">
{/* original dark canvas — a grid + warm glow, not a stock photo */}
<div
aria-hidden="true"
className="absolute inset-0"
style={{
backgroundImage:
"linear-gradient(rgba(255,255,255,0.05) 1px, transparent 1px), " +
"linear-gradient(90deg, rgba(255,255,255,0.05) 1px, transparent 1px)",
backgroundSize: "40px 40px",
maskImage: "radial-gradient(120% 100% at 50% 0%, black 30%, transparent 90%)",
}}
/>
<div
aria-hidden="true"
className="absolute inset-0"
style={{
background:
"radial-gradient(70% 60% at 15% 15%, color-mix(in oklab, #fe6601 35%, transparent) 0%, transparent 60%), " +
"radial-gradient(60% 50% at 100% 100%, color-mix(in oklab, #b44801 30%, transparent) 0%, transparent 65%)",
}}
/>
<div className="relative z-10 flex flex-col gap-10 p-8 @2xl:min-h-[680px] @2xl:flex-row @2xl:items-center @2xl:justify-between @2xl:gap-14 @2xl:p-14">
{/* brand + feature list — plain text on the dark canvas, colour fully
under our own control */}
<div className="flex flex-col gap-8 @2xl:max-w-sm">
<div className="flex items-center gap-2">
<Avatar
name="Brand"
radius="sm"
size="sm"
classNames={{ base: "bg-white/10", name: "text-white" }}
/>
<span className="text-sm font-bold uppercase tracking-[0.2em] text-white">
Brand
</span>
</div>
<div className="flex flex-col gap-2">
<PageTitle
as="h1"
title="Start your 30-day free trial."
classNames={{
base: "flex-col items-start",
title: "text-[2rem] font-bold leading-[1.1] tracking-tight text-white",
}}
/>
<p className="flex items-center gap-1.5 text-sm text-white/60">
<CheckIcon size={14} /> No credit card required
</p>
</div>
<ul className="flex flex-col gap-6">
{features.map((f) => (
<li key={f.title} className="flex gap-3.5">
<Avatar
icon={f.icon as never}
radius="full"
size="sm"
classNames={{ base: "mt-0.5 shrink-0 bg-white/10", icon: "text-white" }}
/>
<div>
<p className="font-semibold text-white">{f.title}</p>
<p className="mt-0.5 text-[0.85rem] leading-relaxed text-white/60">
{f.text}
</p>
</div>
</li>
))}
</ul>
</div>
{/* the actual form — a real, light card floating on the canvas, so
every oks-ui field/label/button renders with the contrast it was
designed for (oks-ui's dark theme only activates via
data-theme="dark" on the document root, not on a nested div —
putting these components directly on a dark background with no
theme flip would render dark-on-dark, illegible text). */}
<div className="w-full rounded-[var(--r-xl)] border border-line bg-paper-raised p-6 shadow-[var(--shadow-lg)] @2xl:max-w-md @2xl:p-8">
<p className="text-center text-[0.85rem] font-medium text-ink-muted">
Register with
</p>
<div className="mt-3 grid grid-cols-3 gap-2">
<Button variant="bordered" size="sm" startContent={<GoogleIcon />}>
Google
</Button>
<Button variant="bordered" size="sm" startContent={<GitHubIcon />}>
GitHub
</Button>
<Button variant="bordered" size="sm" startContent={<ThirdPartyIcon />}>
SSO
</Button>
</div>
<div className="my-5 flex items-center gap-3">
<span className="h-px flex-1 bg-line" aria-hidden="true" />
<span className="text-[0.72rem] font-medium uppercase tracking-wide text-ink-faint">
Or
</span>
<span className="h-px flex-1 bg-line" aria-hidden="true" />
</div>
<Form onSubmit={(data) => console.log(data)} className="flex flex-col gap-4">
<div className="grid grid-cols-2 gap-3">
<FormFieldSet
type="text"
name="firstName"
label="First name"
placeholder="Jane"
validation={{ rules: { required: true } }}
/>
<FormFieldSet
type="text"
name="lastName"
label="Last name"
placeholder="Doe"
validation={{ rules: { required: true } }}
/>
</div>
<FormFieldSet
type="text"
name="username"
label="Username"
placeholder="janedoe"
validation={{ rules: { required: true } }}
/>
<FormFieldSet
type="email"
name="email"
label="Email"
placeholder="you@example.com"
validation={{ rules: { required: true, email: true } }}
/>
<FormFieldSet
type="password"
name="password"
label="Password"
placeholder="Create a password"
description="Minimum length is 8 characters."
validation={{ rules: { required: true, minLength: 8 } }}
/>
<Button type="submit" color="primary" fullWidth>
Sign up
</Button>
</Form>
<p className="mt-4 text-center text-[0.75rem] leading-relaxed text-ink-muted">
By creating an account, you agree to the{" "}
<Button
href="/terms"
variant="link"
size="sm"
className="h-auto p-0 align-baseline text-[0.75rem] font-medium text-accent no-underline"
>
Terms of Service
</Button>
. We’ll occasionally send you account-related emails.
</p>
<p className="mt-4 text-center text-[0.85rem] text-ink-muted">
Already have an account?{" "}
<Button
href="/sign-in"
variant="link"
size="sm"
className="h-auto p-0 align-baseline text-[0.85rem] font-semibold text-accent no-underline"
>
Log in
</Button>
</p>
</div>
</div>
</div>
);
}