LoginScreen
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/login-screen.jsonThe block lands in components/blocks/login-screen.tsx, and what it is made of -
Field, Input,
PasswordField,
Alert, Panel and
Button - in components/ui.
Refused and unreachable are different answers. onSubmit resolves with
nothing when the person is signed in, and with words when the server said no
- “That email and password do not match.” A promise that rejects means there
was no answer at all, and the screen says
words.failedinstead. Telling someone whose password is right that it is wrong sends them to reset it.
<LoginScreen title="Sign in to kilna" mark={<ProductMark product="kilna" size={40} />} onSubmit={async ({ identifier, password }) => { const response = await fetch('/api/session', { method: 'POST', body: JSON.stringify({ identifier, password }) }) if (response.status === 401) return 'That email and password do not match.' if (!response.ok) throw new Error(response.statusText) navigate('/') }} words={words}/>After a refusal the password is selected. The focus goes back to it with
everything in it selected, so the next attempt types over the old one rather
than after it - and the refusal is a role="alert", so a reader whose focus
just moved hears why.
It cannot be sent twice. While an attempt is on its way the button says
words.submitting, is disabled and aria-busy, and a second submission by
any road is refused: a double press signed in twice.
A password manager knows which box is which. The first field is
autocomplete="username" whether it asks for an email or a user name - that
is the token paired with current-password - and identifier="username"
turns it from an email field into a plain one, without the address keyboard
or the browser’s address check.
16px under a finger. iOS zooms the page into a field with smaller text and does not zoom back out, which on this screen is the form at 130% with the password off the side. The set’s fields take care of it, so the screen has nothing to override.
It is the page. The block renders the page’s main. Render it outside the
frame of the signed-in product, where there is no rail beside it.
Props
Read from the types in blocks/login-screen.tsx, the file a product copies - so the description of each prop is also what an editor shows over it there.
| Prop | Type | Default | |
|---|---|---|---|
titlerequired | ReactNode | "Sign in to kilna". | |
description | ReactNode | A line under it, when the title does not say enough. | |
mark | ReactNode | The product's mark, above the title: <ProductMark product="kilna" size={40} />. | |
identifier | email | username | 'email' | What the first field asks for. An email gets the keyboard and the check a browser gives an address; a user name gets neither. |
onSubmitrequired | (credentials: LoginCredentials) => Promise<void | ReactNode> | Resolve with nothing when signed in; with words when refused - "That email and password do not match." Reject when there was no answer. | |
extra | ReactNode | Under the password: "Forgot your password?" | |
footer | ReactNode | Under the panel: "No account? Ask whoever runs this server." | |
wordsrequired | LoginScreenWords | Every string the screen shows: the two field labels, the show and hide names of the password toggle, the submit button in both states, and the message for a server that could not be reached. The block has no words of its own. | |
className | string |
Accessibility
The examples on the stand, through axe 4.14.0 in a browser, in both themes, against WCAG 2.2 A and AA and axe's best practices: 23 rules applied and passed, and none failed. The accessibility report has every item.
Left to a person, where axe could not decide: color-contrast.
History
Each release that changed blocks/login-screen.tsx - the file a product copies - newest first.