This sign-in form is built from REALM UI components in plain HTML, with no build step.
This sign-in form is built from REALM UI components in plain HTML, with no build step.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Login (split) · REALM blocks</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/dist/theme.css">
<script type="module" src="https://cdn.jsdelivr.net/npm/@realmorg/realm@alpha/dist/realm.js"></script>
<style>
body { margin: 0; min-height: 100svh; }
main { min-height: 100svh; display: grid; grid-template-columns: 1fr 1fr; }
.form { display: grid; place-items: center; padding: 2rem; }
.art { background: linear-gradient(135deg, oklch(0.3 0.05 270), oklch(0.15 0.02 270)); display: grid; place-items: end start; padding: 3rem; color: white; }
.art p { margin: 0; font-size: 1.125rem; max-width: 28rem; }
@media (max-width: 860px) { main { grid-template-columns: 1fr; } .art { display: none; } }
</style>
</head>
<body>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/toaster.html"></import-element>
<import-element from="../blocks/blocks.html"></import-element>
<main>
<div class="form"><login-form heading="Welcome back"></login-form></div>
<div class="art"><p>This sign-in form is built from REALM UI components in plain HTML, with no build step.</p></div>
</main>
<ui-toaster></ui-toaster>
</body>
</html>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/button.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/card.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/checkbox.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/field.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/input-otp.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/label.html"></import-element>
<import-element from="https://cdn.jsdelivr.net/npm/@realmorg/ui@alpha/components/spinner.html"></import-element>
<custom-element name="login-form">
<script>
const props = defineProps({ heading: prop.string().default("Log in to your account") });
const login = useState({ busy: false });
async function submit(event) {
event.preventDefault();
const form = event.target;
if (!form.reportValidity()) return;
login.busy = true;
await new Promise((resolve) => setTimeout(resolve, 600));
login.busy = false;
const email = new FormData(form).get("email");
dispatch("login", { email });
document.dispatchEvent(new CustomEvent("toast", { detail: { title: "Signed in", description: `Welcome back, ${email}.` } }));
}
</script>
<style>
:host {
display: block;
width: 100%;
max-width: calc(var(--spacing) * 96);
}
form {
display: grid;
gap: calc(var(--spacing) * 6);
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
}
a {
font-size: var(--text-sm);
color: inherit;
text-underline-offset: 4px;
}
.actions {
display: grid;
gap: calc(var(--spacing) * 3);
}
.actions ui-button {
width: 100%;
}
.footer {
margin: 0;
text-align: center;
font-size: var(--text-sm);
}
</style>
<ui-card>
<ui-card-header>
<ui-card-title level="1"><slot name="@heading"></slot></ui-card-title>
<ui-card-description>Enter your email and password to log in.</ui-card-description>
</ui-card-header>
<ui-card-content>
<form onsubmit="submit(event)" novalidate>
<ui-field>
<ui-label for="email">Email</ui-label>
<ui-input id="email" name="email" type="email" placeholder="m@example.com" required></ui-input>
</ui-field>
<ui-field>
<div class="row">
<ui-label for="password">Password</ui-label>
<a href="#forgot">Forgot your password?</a>
</div>
<ui-input id="password" name="password" type="password" required></ui-input>
</ui-field>
<div class="actions">
<ui-button type="submit" _disabled="#login.busy">
<is-visible when="#login.busy"><ui-spinner size="14"></ui-spinner></is-visible>
Login
</ui-button>
<ui-button variant="outline" type="button">Login with Google</ui-button>
</div>
<p class="footer">Don't have an account? <a href="../signup/">Sign up</a></p>
</form>
</ui-card-content>
</ui-card>
</custom-element>
<custom-element name="signup-form">
<script>
const signup = useState({ agreed: false });
function agree(field) {
signup.agreed = field.checked;
}
function submit(event) {
event.preventDefault();
if (!event.target.reportValidity()) return;
const name = new FormData(event.target).get("name");
document.dispatchEvent(new CustomEvent("toast", { detail: { title: "Account created", description: `Welcome, ${name}!` } }));
}
</script>
<style>
:host {
display: block;
width: 100%;
max-width: calc(var(--spacing) * 104);
}
form {
display: grid;
gap: calc(var(--spacing) * 5);
}
.pair {
display: grid;
grid-template-columns: 1fr 1fr;
gap: calc(var(--spacing) * 4);
}
ui-button {
width: 100%;
}
</style>
<ui-card>
<ui-card-header>
<ui-card-title level="1">Create an account</ui-card-title>
<ui-card-description>Enter your details to get started.</ui-card-description>
</ui-card-header>
<ui-card-content>
<form onsubmit="submit(event)" novalidate>
<div class="pair">
<ui-field><ui-label for="name">Name</ui-label><ui-input id="name" name="name" required></ui-input></ui-field>
<ui-field><ui-label for="company">Company</ui-label><ui-input id="company" name="company"></ui-input></ui-field>
</div>
<ui-field><ui-label for="signup-email">Email</ui-label><ui-input id="signup-email" name="email" type="email" required></ui-input></ui-field>
<ui-field>
<ui-label for="signup-password">Password</ui-label>
<ui-input id="signup-password" name="password" type="password" minlength="8" required></ui-input>
<ui-field-description>At least 8 characters.</ui-field-description>
</ui-field>
<ui-checkbox name="terms" required onchange="agree(this)">I agree to the terms and privacy policy</ui-checkbox>
<ui-button type="submit" _disabled="!#signup.agreed">Create account</ui-button>
</form>
</ui-card-content>
</ui-card>
</custom-element>
<custom-element name="otp-form">
<script>
const otp = useState({ status: "" });
function complete(event) {
otp.status = event.detail.value === "123456" ? "Verified." : "That code is not valid. Try 123456.";
}
</script>
<style>
:host {
display: block;
max-width: calc(var(--spacing) * 96);
}
.center {
display: grid;
justify-items: center;
gap: calc(var(--spacing) * 4);
}
p {
margin: 0;
font-size: var(--text-sm);
color: var(--muted-foreground);
}
</style>
<ui-card>
<ui-card-header>
<ui-card-title level="1">Verify your email</ui-card-title>
<ui-card-description>We sent a 6-digit code to your inbox.</ui-card-description>
</ui-card-header>
<ui-card-content class="center">
<ui-input-otp length="6" separator="3" oncomplete="complete(event)"></ui-input-otp>
<p role="status"><slot name="#otp.status">Enter the code to continue.</slot></p>
</ui-card-content>
</ui-card>
</custom-element>