Menu
Overview
General
ButtonBadgePillsAvatarBrand lockupLayout
ContainerGrid & RowSurface & text utilitiesApp shellPage headerData Display
CardTableChartsCalendarStatsBulletsLeadersLogo wallCollapse & DisclosureTimelineValue chainDescriptionsFeedback
AlertProgressModalSlide-overSkeletonEmpty stateForms
Field & InputDropdownLead form & CTA rowDate pickerFilter barNavigation
NavSide navigationTop navigationApp switcherUser menuStepperTabsBreadcrumbPaginationTemplates
Sign-in pageSigned-out pageSettings layoutTheming
Browser tabProduct themesTemplates
Sign-in page
Split sign-in with the ambient brand panel, the three-screen forgot-password flow, and both SSO shapes: optional under the divider, or the only way in.
.authpage · .authsplit · .authbrand · .authcard · .pwgroup · .pwtoggle · .authcard__back · .authcard__done · .authcard__sso--primary · .field__req
<div class="authpage">
<div class="authsplit">
<aside class="authbrand">
<svg class="authhero__art" viewBox="0 0 600 900" preserveAspectRatio="xMidYMid slice" aria-hidden="true">
<path class="authhero__trace" d="M-120 760 L420 120"/>
<path class="authhero__trace" d="M-40 860 L560 160"/>
<path class="authhero__trace" d="M60 900 L680 220"/>
<path class="authhero__mark" d="M96 196 l52 52 -52 52 -52 -52 Z"/>
<path class="authhero__mark" d="M404 236 l40 40 -40 40 -40 -40 Z"/>
</svg>
<div class="authbrand__inner">
<span class="theme-novapay vlogo"><svg class="vmark" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3.5" y="6" width="17" height="12" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><rect x="6" y="12" width="3.6" height="3" rx="0.6"/></svg><span class="wm"><span class="nm">nova</span><span class="sf">pay</span></span></span>
<p class="authbrand__tagline">Digital payments, full stack: switching and routing, card acquiring, QR and NFC, reconciliation and settlement.</p>
<ul class="authbrand__modules" aria-label="novapay capabilities"><li>Switching & routing</li><li>Card acquiring</li><li>QR / NFC</li></ul>
</div>
</aside>
<div class="authmain">
<form class="card authcard" action="sign-in-page.html">
<svg class="authcard__mark" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="6" width="17" height="12" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><rect x="6" y="12" width="3.6" height="3" rx="0.6"/></svg>
<h2 class="authcard__title">Sign in to your console</h2>
<p class="muted authcard__sub">Use the account your administrator issued you.</p>
<div class="field">
<label for="si-user">Username<span class="field__req" aria-hidden="true">*</span></label>
<input class="input" id="si-user" name="username" autocomplete="username" required>
</div>
<div class="field">
<label for="si-pass">Password<span class="field__req" aria-hidden="true">*</span></label>
<div class="pwgroup">
<input class="input" id="si-pass" name="password" type="password" autocomplete="current-password" required>
<button type="button" class="pwtoggle" data-password-toggle aria-controls="si-pass" aria-pressed="false" aria-label="Show password"><svg class="icon pwtoggle__show" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/></svg><svg class="icon pwtoggle__hide" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.9 5.7A9.9 9.9 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-2.4 3.2M6.5 6.6A16.6 16.6 0 0 0 2.5 12s3.5 6.5 9.5 6.5a9.4 9.4 0 0 0 5.5-1.7"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2M3 3l18 18"/></svg></button>
</div>
</div>
<button class="btn btn--primary btn--lg authcard__submit">Sign in</button>
<p class="authalt">or</p>
<button type="button" class="btn btn--secondary authcard__sso">Continue with single sign-on</button>
<p class="authcard__forgot"><a class="authcard__back" href="sign-in-page.html">Forgot password?</a></p>
<div class="authcard__foot"><span class="muted">Managed by</span><span class="brandmark" role="img" aria-label="Novus Technologies"></span></div>
</form>
</div>
</div>
</div>
<div class="authpage">
<div class="authmain">
<form class="card authcard" action="sign-in-page.html">
<svg class="authcard__mark" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="6" width="17" height="12" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><rect x="6" y="12" width="3.6" height="3" rx="0.6"/></svg>
<h2 class="authcard__title">Sign in to your console</h2>
<p class="muted authcard__sub">Use the account your administrator issued you.</p>
<div class="field field--error">
<label for="si-user-e">Username<span class="field__req" aria-hidden="true">*</span></label>
<input class="input" id="si-user-e" name="username" autocomplete="username" required value="ops.admin" aria-describedby="si-error">
<p class="field__error" id="si-error" role="alert">Wrong username or password.</p>
</div>
<div class="field">
<label for="si-pass-e">Password<span class="field__req" aria-hidden="true">*</span></label>
<div class="pwgroup">
<input class="input" id="si-pass-e" name="password" type="password" autocomplete="current-password" required>
<button type="button" class="pwtoggle" data-password-toggle aria-controls="si-pass-e" aria-pressed="false" aria-label="Show password"><svg class="icon pwtoggle__show" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/></svg><svg class="icon pwtoggle__hide" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.9 5.7A9.9 9.9 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-2.4 3.2M6.5 6.6A16.6 16.6 0 0 0 2.5 12s3.5 6.5 9.5 6.5a9.4 9.4 0 0 0 5.5-1.7"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2M3 3l18 18"/></svg></button>
</div>
</div>
<button class="btn btn--primary btn--lg authcard__submit">Sign in</button>
<p class="authalt">or</p>
<button type="button" class="btn btn--secondary authcard__sso">Continue with single sign-on</button>
<p class="authcard__forgot"><a class="authcard__back" href="sign-in-page.html">Forgot password?</a></p>
<div class="authcard__foot"><span class="muted">Managed by</span><span class="brandmark" role="img" aria-label="Novus Technologies"></span></div>
</form>
</div>
</div>
Forgot password
<div class="authpage">
<div class="authmain">
<form class="card authcard" action="sign-in-page.html">
<svg class="authcard__mark" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="6" width="17" height="12" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><rect x="6" y="12" width="3.6" height="3" rx="0.6"/></svg>
<h2 class="authcard__title">Reset your password</h2>
<p class="muted authcard__sub">Enter your username and we will send a reset link to the email on file.</p>
<div class="field">
<label for="fp-user">Username<span class="field__req" aria-hidden="true">*</span></label>
<input class="input" id="fp-user" name="username" autocomplete="username" required>
</div>
<button class="btn btn--primary btn--lg authcard__submit">Send reset link</button>
<p class="authcard__forgot"><a class="authcard__back" href="sign-in-page.html">Back to sign in</a></p>
</form>
</div>
</div>
Check your email
If an account exists for that username, a reset link is on its way. It expires in 30 minutes.
<div class="authpage">
<div class="authmain">
<div class="card authcard">
<div class="authcard__done">
<span class="authcard__check"><svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12.5 4.5 4.5L19 7.5"/></svg></span>
<h2 class="authcard__title">Check your email</h2>
<p class="muted authcard__sub">If an account exists for that username, a reset link is on its way. It expires in 30 minutes.</p>
</div>
<div class="authcard__links">
<button type="button" class="btn btn--secondary">Resend link</button>
<a class="btn btn--primary" href="sign-in-page.html">Back to sign in</a>
</div>
</div>
</div>
</div>
<div class="authpage">
<div class="authmain">
<form class="card authcard" action="sign-in-page.html">
<div class="alert alert--danger" role="alert">This reset link has expired. Request a new one below.</div>
<h2 class="authcard__title" style="margin-top:var(--space-4)">Choose a new password</h2>
<p class="muted authcard__sub">Eight characters or more. It cannot match any of your last five.</p>
<div class="field">
<label for="fp-new">New password<span class="field__req" aria-hidden="true">*</span></label>
<div class="pwgroup">
<input class="input" id="fp-new" name="new-password" type="password" autocomplete="new-password" required>
<button type="button" class="pwtoggle" data-password-toggle aria-controls="fp-new" aria-pressed="false" aria-label="Show password"><svg class="icon pwtoggle__show" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2.5 12S6 5.5 12 5.5 21.5 12 21.5 12 18 18.5 12 18.5 2.5 12 2.5 12Z"/><circle cx="12" cy="12" r="3"/></svg><svg class="icon pwtoggle__hide" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.9 5.7A9.9 9.9 0 0 1 12 5.5c6 0 9.5 6.5 9.5 6.5a17 17 0 0 1-2.4 3.2M6.5 6.6A16.6 16.6 0 0 0 2.5 12s3.5 6.5 9.5 6.5a9.4 9.4 0 0 0 5.5-1.7"/><path d="M9.9 9.9a3 3 0 0 0 4.2 4.2M3 3l18 18"/></svg></button>
</div>
</div>
<div class="field">
<label for="fp-confirm">Confirm new password<span class="field__req" aria-hidden="true">*</span></label>
<input class="input" id="fp-confirm" name="confirm-password" type="password" autocomplete="new-password" required>
</div>
<button class="btn btn--primary btn--lg authcard__submit">Save password</button>
<p class="authcard__forgot"><a class="authcard__back" href="sign-in-page.html">Request a new link</a></p>
</form>
</div>
</div>
Single sign-on
<div class="authpage">
<div class="authmain">
<form class="card authcard" action="sign-in-page.html">
<svg class="authcard__mark" viewBox="0 0 24 24" aria-hidden="true"><rect x="3.5" y="6" width="17" height="12" rx="2"/><line x1="3.5" y1="9.5" x2="20.5" y2="9.5"/><rect x="6" y="12" width="3.6" height="3" rx="0.6"/></svg>
<h2 class="authcard__title">Sign in to your console</h2>
<p class="muted authcard__sub">This workspace signs in through your organisation's identity provider.</p>
<button class="btn btn--lg authcard__sso authcard__sso--primary">Continue with single sign-on</button>
<p class="authcard__note">Accounts for this workspace are managed by your administrator.</p>
</form>
</div>
</div>
<div class="authpage">
<div class="authmain">
<form class="card authcard" action="sign-in-page.html" style="max-width:28.5rem">
<div class="alert alert--danger" role="alert">Single sign-on failed: your session at the identity provider expired. Try again.</div>
<h2 class="authcard__title" style="margin-top:var(--space-4)">Sign in to your console</h2>
<p class="muted authcard__sub">Use the account your administrator issued you.</p>
<button class="btn btn--primary btn--lg authcard__submit">Retry single sign-on</button>
<p class="authcard__forgot"><a class="authcard__back" href="sign-in-page.html">Sign in with a password instead</a></p>
</form>
</div>
</div>
Usage
- Sign-in sits on the kit's near-white ground (
--bg-subtle) like every other screen, with the card lifted off it by a border and a soft shadow. The deep brand panel the kit used earlier is gone. - The split is 50/50 from 900px and stacks below it, brand panel first.
- The brand panel carries ambient line art: slow loops drawn from tokens in light tints, the one decoration the kit allows, and it stops under
prefers-reduced-motion. It never shifts layout or takes pointer events. - The lockup keeps its normal light-mode treatment here, since the panel is a light surface; no dark-lockup swap is needed.
- Single sign-on sits under a labelled divider as a secondary action, so the password form keeps the one primary button.
- No theme toggle on this page: the theme belongs in the user menu after sign-in, and portals render light by default.
- The brand panel carries the product lockup in the dark-lockup treatment, one line of promise, and at most five capability chips. Below 900px it stacks above the card.
- The title names what is being signed in to, followed by one line of sub-copy. Labels are small caps with a red required marker; inputs are filled so they read as places to type.
- One full-width primary action. The reset link sits centred under it, and the Novus endorsement signs the card off.
- Wrong credentials show one inline error under the username and keep what was typed. Never clear the form.
- The password toggle is a real button named "Show password" or "Hide password"; the console layer hides it when scripting is unavailable, so a page never shows a dead control.
Usage: forgot password
- Forgot password is its own screen, not a disclosure on the sign-in card. The link and every step's back link are the same affordance:
.authcard__back, full width, centred, accent, underlined. - Three screens: request (username only) → link sent → choose a new password. The brand panel drops away from step one — once someone has decided they are locked out, the artwork is in the way.
- Step two never confirms an account exists. "If an account exists for that username" is the whole message, so the screen cannot be used to enumerate users; it names the inbox and the expiry, not the person.
- Step three pairs the two fields and their
autocompletetokens (new-password, notcurrent-password), so a password manager offers to generate one instead of filling the old. - A bad or expired link is an
.alert--dangerat the top of step three with the request form still usable underneath — never a dead end that sends the user back to the start. - Password policy is one line of sub-copy. Do not build a live rules checklist: the server is the authority and a client-side list always disagrees with it eventually.
Usage: single sign-on
- Where SSO is one option among several it sits under the
ordivider as a secondary button, and the password form keeps the view's one primary. - Where the workspace is SSO-only, delete the password form rather than disabling it, and promote the provider button with
.authcard__sso--primary. A greyed-out password box reads as a broken login. - A failed SSO round trip is an
.alert--dangernaming what happened in the provider's terms, plus a retry that stays primary. Always keep the way back to password sign-in underneath. - Redirects are a loading state, not a screen: put
data-loading="true"on the button that was pressed, or the user clicks twice and opens two tabs. - The provider name goes in the button ("Continue with Okta"), never a generic SSO icon alone. The user must know where they are being sent before they leave.
CSS
These rules ship in the console pattern layer, admin-kits/shared/novus-admin.css, which this site and every Admin Kit flavor load after tokens.css. Copy the whole layer, or only the sections below; every value is a token.
/* Authentication screens sit on the kit's near-white ground like every other
screen (feature 009, replacing the deep brand panel from 007). The ambient
line work carries the interest. Flat colour: no gradients, no glow. */
.authpage { min-height: 100vh; min-height: 100dvh; display: flex; flex-direction: column; background: var(--bg-subtle); }
.authsplit { flex: 1; display: flex; flex-direction: column; }
.authbrand { position: relative; overflow: hidden; display: flex; align-items: center; padding: var(--space-6) var(--gutter); color: var(--text-secondary); }
/* Ambient authentication art: slow looping line work on the flat brand ground,
the one decorative motion the kit allows (constitution 1.12.0, Principle II). */
.authhero__art { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.authhero__trace { fill: none; stroke: var(--blue-200); stroke-width: 1; opacity: 0.9; stroke-dasharray: 220 900; animation: authtrace 13s linear infinite; }
.authhero__trace:nth-of-type(2) { animation-delay: -4s; }
.authhero__trace:nth-of-type(3) { animation-delay: -8s; }
.authhero__mark { fill: none; stroke: var(--blue-200); stroke-width: 1.5; opacity: 0.75; animation: authmark 9s ease-in-out infinite; transform-origin: center; }
.authhero__mark:nth-of-type(5) { animation-delay: -4.5s; }
.authbrand__inner { position: relative; }
@keyframes authtrace { from { stroke-dashoffset: 1120; } to { stroke-dashoffset: 0; } }
@keyframes authmark { 0%, 100% { opacity: 0.45; } 50% { opacity: 0.85; } }
@media (prefers-reduced-motion: reduce) {
.authhero__trace, .authhero__mark { animation: none; }
}
.authbrand__inner { width: 100%; max-width: 28rem; margin-inline: auto; display: flex; flex-direction: column; gap: var(--space-4); }
/* The lockup keeps its normal treatment: the panel is a light surface now. */
.authpage .authbrand .vlogo .vmark { width: 2.5rem; height: 2.5rem; }
.authpage .authbrand .wm { font-size: var(--text-2xl); }
.authbrand__tagline { margin: 0; font-size: var(--text-lg); line-height: var(--lh-lg); color: var(--text-secondary); }
.authbrand__modules { list-style: none; display: flex; flex-wrap: wrap; gap: var(--space-2); margin: 0; padding: 0; }
.authbrand__modules li { display: inline-flex; align-items: center; min-height: 2rem; padding: var(--space-1) var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-pill); background: var(--surface); color: var(--text-secondary); font-size: var(--text-sm); white-space: nowrap; }
.authmain { flex: 1; display: grid; place-items: center; padding: var(--space-2) var(--gutter) var(--space-7); }
@media (min-width: 900px) {
.authsplit { flex-direction: row; }
.authbrand { flex: 1 1 50%; padding: var(--space-8); }
.authmain { flex: 1 1 50%; padding: var(--space-7) var(--gutter); }
}
.authcard { width: 100%; max-width: 28.5rem; padding: var(--space-6); border-radius: var(--radius-xl); box-shadow: var(--shadow-2); border: 1px solid var(--border); }
@media (min-width: 480px) { .authcard { padding: var(--space-6) var(--space-7); } }
.authcard__mark { display: block; width: 2.75rem; height: 2.75rem; margin: 0 0 var(--space-5); fill: none; stroke: var(--accent-text); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.authcard__title { margin: 0 0 var(--space-2); font-size: var(--text-2xl); line-height: var(--lh-2xl); text-align: start; }
.authcard__sub { margin: 0 0 var(--space-5); font-size: var(--text-sm); line-height: var(--lh-sm); }
.authcard .field > label { font-size: var(--text-xs); line-height: var(--lh-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide); }
.field__req { margin-inline-start: var(--space-1); color: var(--danger-text); }
.authcard .input { min-height: 2.75rem; background: var(--bg-subtle); border-color: var(--border); }
.authcard .input:hover { background: var(--bg-muted); }
.authcard .input:focus-visible { background: var(--surface); }
.authcard .field--error .input { border-color: var(--danger); }
.authcard .field__error { margin: 0; }
.pwgroup { position: relative; display: block; }
.pwgroup > .input { padding-inline-end: 3rem; }
.pwtoggle { position: absolute; inset-inline-end: var(--space-1); top: 50%; transform: translateY(-50%); display: none; align-items: center; justify-content: center; width: 2.75rem; height: 2.75rem; padding: 0; border: 0; border-radius: var(--radius-md); background: transparent; color: var(--text-secondary); cursor: pointer; }
@media (scripting: enabled) { .pwtoggle { display: inline-flex; } }
.pwtoggle:hover { color: var(--text); background: var(--bg-muted); }
.pwtoggle:focus-visible { outline: none; box-shadow: var(--focus-ring); color: var(--text); }
.pwtoggle .pwtoggle__hide, .pwtoggle[aria-pressed="true"] .pwtoggle__show { display: none; }
.pwtoggle[aria-pressed="true"] .pwtoggle__hide { display: inline-block; }
.authcard__submit { width: 100%; min-height: 2.75rem; margin-top: var(--space-2); font-size: var(--text-base); }
.authalt { display: flex; align-items: center; gap: var(--space-3); margin: var(--space-4) 0 var(--space-3); color: var(--text-muted); font-size: var(--text-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide); }
.authalt::before, .authalt::after { content: ""; flex: 1; height: 1px; background: var(--border); }
.authcard__sso { width: 100%; min-height: 2.75rem; }
/* Forgot password is a screen in this flow, not a disclosure on the card, so the
link and the step's back link are the same affordance: centred, accent, underlined. */
.authcard__forgot { margin-top: var(--space-3); font-size: var(--text-sm); text-align: center; }
.authcard__forgot > summary, .authcard__back { display: inline-flex; align-items: center; justify-content: center; min-height: 2.75rem; color: var(--accent-text); text-decoration: underline; cursor: pointer; }
.authcard__back { width: 100%; }
.authcard__forgot > summary { list-style: none; }
.authcard__forgot > summary::-webkit-details-marker { display: none; }
.authcard__forgot:focus-visible, .authcard__forgot > summary:focus-visible, .authcard__back:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius-sm); }
.authcard__forgot > p { margin: 0 auto; max-width: 22rem; }
/* The one auth state with no fields: a circled check, the headline, then quiet links.
Centred because there is nothing to fill and nothing to align to. */
.authcard__done, .authcard__done > .authcard__title { text-align: center; }
.authcard__done > .authcard__check { display: grid; place-items: center; width: 3rem; height: 3rem; margin: 0 auto var(--space-4); border-radius: var(--radius-pill); background: var(--success-subtle); color: var(--success-text); }
.authcard__done > .authcard__check > .icon { width: 1.5rem; height: 1.5rem; }
/* Its two actions sit side by side as equal buttons, never stacked: the primary is the
next step (back to sign in), the secondary the fallback (resend). */
.authcard__links { display: flex; gap: var(--space-3); margin-top: var(--space-4); padding-top: var(--space-4); border-top: 1px solid var(--border); }
.authcard__links > .btn { flex: 1; min-height: 2.75rem; }
/* An SSO-only workspace drops the password form entirely, so the provider button
takes the primary slot instead of sitting under an "or". */
.authcard__sso--primary { background: var(--accent); color: var(--text-on-accent); }
.authcard__sso--primary:hover { background: var(--accent-hover); border-color: transparent; }
.authcard__note { margin: var(--space-3) 0 0; font-size: var(--text-sm); text-align: center; color: var(--text-muted); }
.authcard__back { width: 100%; margin-top: var(--space-3); }
.authcard__foot, .signedout__foot { display: flex; align-items: center; justify-content: center; gap: var(--space-2); font-size: var(--text-sm); }
.authcard__foot { margin-top: var(--space-4); }
.authcard__foot > .muted, .signedout__foot > .muted { white-space: nowrap; }
.authcard__foot .brandmark, .signedout__foot .brandmark { width: 108px; }