Skip to main content

<Form>

<Form> renders a configurable form that can be used to sign in or sign up users. A <ConfigurationProvider> needs to be rendered as a parent component, preferably near the application root component to reduce the number of re-renders.

Usage

import { ConfigurationProvider, Form } from "@slashid/react"
import "@slashid/react/style.css"

function SignIn() {
return (
<ConfigurationProvider>
<Form />
</ConfigurationProvider>
)
}

The default styles must be imported:
import "@slashid/react/style.css"

Note: if using Parcel the CSS import line should be changed to:
import "@slashid/react/dist/style.css"

Props

NameTypeDefaultDescription
onSuccess?(user: User) => voidCallback function that gets called with a User object returned from core SDK upon successful log in action
onError?(error: Error, context: ErrorContext) => void;Callback function that gets called with a User object returned from core SDK upon log in action failure
middleware?LoginMiddleware | LoginMiddleware[]Effects to be run post-login but before onSuccess fires, and before the next render cycle. See LoginMiddleware for more.
factors?Factor[]Override for the available authentication factors set by <ConfigurationProvider>.
text?Record<string, string>Overrides for the text shown in the form. See <ConfigurationProvider> default text key-value pairs.
children?Slot<"initial" | "authenticating" | "error" | "success" | "footer">Named slots to be replaced with custom implementation.

Interfaces

When working with the onError prop, the ErrorContext type can be used to determine the context in which the error occurred. The following table lists the possible values of the context property:

type ErrorContext = {
config: LoginConfiguration // handle and factor used to log in
options?: LoginOptions // any middleware used to configure login
attempt: number // number of login attempts
error: Error // the error that occurred
}

Configuration should be done through the <ConfigurationProvider>, but if you wish you can override it using the factors and text props.

UI customization

The primary way of customizing the form appearance is by using CSS custom properties. Additionally, the individual form element styles can be overridden using plain CSS.

CSS custom properties (variables)

A set of CSS custom properties is used to define the basic look and feel of the <Form>. There are two ways of overriding the default values of these properties:

  • use a global CSS selector: sid-theme-root
  • passing in a custom class name to the <Form> using the className prop

Both of these approaches can be used to specify any of the following properties values:

--sid-color-foreground
--sid-color-background
--sid-color-primary
--sid-color-primary-hover
--sid-color-secondary
--sid-color-tertiary
--sid-color-mute
--sid-color-panel
--sid-color-contrast
--sid-color-placeholder
--sid-color-smooth
--sid-color-subtle
--sid-color-soft
--sid-color-offset
--sid-color-transparent
--sid-font-family
--sid-button-border-radius

For example, in order to the change the font-family you could specify the following:

.sid-theme-root {
--sid-font-family: Inter
}

Overriding CSS rules

If you inspect the form elements using the developer tools in your browser, you'll notice that they have a set of class names applied. Within this set some class names are stable, while others are hash based and dynamic. Class names prefixed by sid-- are stable and act as a public API for overriding the CSS rules. For example, you could customize the button element by using a selector like this:

.sid-button {
color: red;
}

Layout slots & primitives

The form layout is composed of a set of named slots that can be used to customize the layout. The slots and their rendering conditions are as follows:

  • initial - rendered when the form is first displayed, can be used to render the form fields
  • authenticating - rendered while the user is being authenticated
  • error - rendered when an error occurs
  • success - rendered when the user is successfully authenticated
  • footer - rendered at the bottom of the form, regardless of the form state

When rendering Form without replacing any slots with custom components, it will render in its default state. The following screenshot shows the default form layout in the initial state with the slots outlined:

Form slots

The following example shows how to use these slots:

import { Slot, Form } from "@slashid/react"

function CustomForm() {
return (
<Form>
<Slot name="authenticating">
<p>Please wait...</p>
</Slot>
<Slot name="success">
<p>You are logged in!</p>
</Slot>
<Slot name="error">
<p>An error happened!</p>
</Slot>
<Slot name="footer">
<footer>My custom footer</footer>
</Slot>
</Form>
)
}

We haven't used the initial slot in this example, so the default form fields will be rendered. If you also want to customise the way the form fields are being rendered, you can compose the <Form> using the primitives we export under the Form namespace.

Composing a custom Form

tip

Check the Form composition guide for a more detailed explanation of how to compose a custom form.

When replacing the default form fields using <Slot name="initial">, you can use a set of primitives designed to make it easier to compose the form. In order to use these knowing a couple of interfaces will be helpful:

Interfaces
Primitives - Form.Initial namespace

The primitive components used to build out the initial state of the form are all exposed under the Form.Initial namespace. This namespace has the following components:

ComponentChildren as a function propsDescription
Form.Initial.LogoRenders the configured logo.
Form.Initial.HeaderRenders the header with text content.
Form.Initial.Controls{ factors: Factor[], handleTypes: HandleType[], handleSubmit: (factor: Factor, handle?: Handle) => void }Wraps children with a <form> that has a submit listener attached.
Form.Initial.SSORenders a button per each configured SSO factor.

The form is designed to be composed of these primitives. You can add additional components of your own anywhere within the slot.

The Form.Initial.Controls component is the most complex one, as it renders the form fields and the submit button. We can break the form down further by passing children to this component. There are two options for passing children to this component:

  • building a completely custom form by passing in a function as a child
  • passing in a ReactNode as a child and composing the form fields using the primitives from the Form.Initial.Controls namespace and your components

The following table lists the components that can be used to compose the form fields:

ComponentChildren as a function propsDescription
Form.Initial.Controls.Input{ factors: Factor[], handleTypes: HandleType[] }Renders the appropriate form fields based on the factors being used.
Form.Initial.Controls.Submit{ status: FormStatus }Renders the submit button.

You can freely mix and match these components to create your own form layout. Any children passed to <Slot name="initial"> will be rendered accordingly, including the primitives listed above and your own components.

Alternatively they accept children as a function, which is called with the props listed above. This will make you responsible for rendering the UI, while giving you access to the props to reuse the behaviour of the default implementation. For example:

function ComposedForm({ handleSubmit }) {
const [email, setEmail] = useState("")

return (
<form
onSubmit={(e) => {
e.preventDefault()
handleSubmit({ method: "email_link" }, { type: "email_address", value: email })
}}
>
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} />
<Form.Initial.Controls.Submit />
</form>
)
}
Primitives - Form.Error namespace

This namespace contains a single component used to render the UI of the Form when an error occurs:

ComponentChildren as a function propsDescription
Form.Error{ context: ErrorContext, retry: () => void, cancel: () => void }Renders the form UI in case of an error.

When passing a function as children to this component, this function will be called with the same ErrorContext object as the onError prop. This gives you access to the underlying error object and other useful metadata like the number of login attempts. The retry and cancel functions can be used to retry the login action or cancel it respectively. retry will submit the form with the same payload as it was used in the original attempt, while cancel will reset the form to its initial state.

Composition example

We first created our own <ComposedForm> component that renders a form with a custom email input and a submit button that uses the default implementation. We then wrap this component with <Form.Initial.Controls> to get access to the handleSubmit prop, which we use to submit the form:

import { Slot, Form } from "@slashid/react"

function CustomForm() {
return (
<Form>
<Slot name="initial">
<Form.Initial.Controls>
{({ handleSubmit }) => {
return <ComposedForm handleSubmit={handleSubmit} />
}}
</Form.Initial.Controls>
</Slot>
</Form>
)
}

In this contrived example we illustrated how to compose the form using our primitives or your own components. In practice you'll probably want to use the default implementation for most of the form fields, and only replace the ones you want to customize.

Multi-Factor Authentication

See <MultiFactorAuth> component reference.