Skip to content

LoginScreen

Terminal window
$ npx shadcn@latest add https://lacodda.github.io/dowel/r/login-screen.json

The block lands in components/blocks/login-screen.tsx, and what it is made of - Field, Input, PasswordField, Alert, Panel and Button - in components/ui.

See it liveSign in, be refused, find the server out of reach - at a desktop's width and a phone's.→

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.failed instead. 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.

PropTypeDefault
titlerequiredReactNode"Sign in to kilna".
descriptionReactNodeA line under it, when the title does not say enough.
markReactNodeThe product's mark, above the title: <ProductMark product="kilna" size={40} />.
identifieremail | 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.
extraReactNodeUnder the password: "Forgot your password?"
footerReactNodeUnder the panel: "No account? Ask whoever runs this server."
wordsrequiredLoginScreenWordsEvery 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.
classNamestring

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.

  1. v0.41.0
    • docs read every props table from the component's types ee3b159
  2. v0.36.0
    • feat SettingsScreen, LoginScreen and EmptyDashboardScreen 3196240