Inputs
OtpInput
The OTP input collects a one-time passcode, the short numeric or alphanumeric code that a multi factor authentication flow sends over SMS or email, as a row of single character boxes bound to one string value. Typing a character advances the focus to the next box, Backspace and Delete take the code apart character by character, the arrow keys along with Home and End move between the boxes, pasting a whole code cleans it up and spreads it over them (a transformer of your own can even pull the code out of the message it was copied inside of), copying or cutting from any box hands over the whole code rather than the one character it holds, and on supporting browsers the code that just arrived by SMS is filled in automatically. The set of characters it accepts is narrowed down by the input type or by a regular expression, upper or lower cased on the way in, and normalized to ASCII when it arrives written in another numbering system; what is rejected outright is reported rather than silently dropped. The code is masked with a character of your own choosing, split into groups by a separator, laid out horizontally or vertically in either direction, kept free of holes, and the whole row can be turned into a single stop of the tab order. The boxes form a labelled and described accessible group, each announcing its own position. It completes with a callback to submit from, takes part in an EditForm like any other input, shows the wait while the code is being checked, and paints the error state of a code that the server sent back as wrong.
Usage
Every example is live. Open its code to see exactly what produced the component running underneath.
Basic
Label & description
label element bound to the
first box, so clicking it moves the focus there, and the group of inputs is named after it for screen
readers. LabelTemplate replaces the text with arbitrary markup while keeping the same wiring,
and Required marks the label with an asterisk and the boxes as required for the browser and for
assistive technologies.
aria-describedby, so a screen reader announces it along with the name of the group instead
of leaving it as text that only sighted users get. It is deliberately attached to the group and not to
each box, which would otherwise repeat the whole sentence at every character of the code.
DescriptionTemplate replaces it with arbitrary markup, so a "resend the code" action or a
countdown put in there is announced with the group just the same.
Type & keyboard
123-456 still lands in the
boxes as 123456. It is deliberately not rendered as a native number input, which would
carry spin buttons, react to the mouse wheel and silently report an empty value for the characters a
number accepts but a code does not, such as e or -.
Password masks the characters with the bullet of the browser, which is what a code that stays
on screen for a while should do.
inputmode.
Tel is the exception and is rendered as it is, since it validates nothing.
۱۲۳۴۵۶, one in Arabic as ١٢٣٤٥٦, and a keyboard set to those languages types
them that way. NormalizeDigits turns the digits of every other numbering system into their
ASCII form as they are typed or pasted, which is what keeps such a code from being refused as if it
were not a number at all. The conversion happens before the Type and the Pattern decide
what to accept, and the value of the component is the ASCII form that a server expects.
۱۲۳۴۵۶ or ١٢٣٤٥٦.Mask
Password Type, which browsers refuse to let anyone copy from, is treated the
same way. Pasting into the boxes keeps working either way.
Pattern
Placeholder
000000 or
ABCDEF can be shown; any other value is repeated in every box as is.
Separator
BitInputType.Number or a Pattern that rejects them is set, and are
taken as characters of the code otherwise.
123-456.
Directions
Merged
Accessibility
group that is named after the Label, or after
AriaLabel when there is none, so a screen reader announces the purpose of the code once instead
of once per box. Each box is then named after its own position, 1 of 6 by default, which is
what tells the user which character they are on. InputAriaLabelFormat is the composite format
behind that name, where {0} is the one based position and {1} is the
Length, and it is what localizes the announcement.
aria-describedby
pointing at the helper text under the boxes, so the sentence that says where the code was sent and
how long it is good for is announced along with the name of the group rather than being left as
text that only sighted users get.
aria-invalid to them, IsLoading marks the group with aria-busy so
that the wait for the answer of the server is announced rather than only drawn, the separators are
hidden from screen readers so the code is never read out with the dashes in it, and a disabled
component drops out of the tab order altogether.
Autofill, paste & copy
autocomplete="one-time-code", which is what makes iOS and Safari offer the code that has
just arrived by SMS; on browsers that implement the WebOTP API the component asks the browser for that
code itself and fills the boxes as soon as the user allows it; and a paste anywhere in the row is
caught and spread over the boxes. NoSmsAutoFill turns the first two off for a code that never
arrives by SMS, such as one read from an authenticator app.
123-456 or together with the words around it be pasted as is instead of being refused.
Uppercase applies to it too.
your code is A1B2C3 would fill the boxes with YOURCODEI.
PasteTransformer is the way out. It is a function applied to a chunk of characters that
reaches the component in one go, before anything else is done with it, so a single regular
expression picks the code out of the sentence. Returning an empty string rejects the whole chunk,
which raises OnInvalid, and it is deliberately not applied to a single typed character.
autocomplete of
off is a request the browser extensions deliberately ignore, since it is what a site
refusing to work with them looks like, so the attributes that 1Password, LastPass, Bitwarden and
Dashlane read instead are rendered along with it. They are not rendered otherwise, because offering
the code from the authenticator vault that issued it is precisely what a password manager is for.
123 456, 12-34-56 or your code is 123456:your code is A1B2C3, do not share it:Binding
null or to an empty string clears them all. DefaultValue is the uncontrolled
counterpart: it seeds the boxes on the first render and then leaves them to the user.
Events
Public API
Validation
EditContext, reports its value on every keystroke, renders the
error state on the boxes, and marks itself with aria-invalid for assistive technologies.
Data annotations such as [Required] and [MinLength] on the bound property are
all that is needed to validate the length of the code. Outside of an EditForm, Name
posts the whole code as one named field of a plain HTML form, not one field per box.
Invalid
EditContext taking any part in it. It draws them in the error color, keeps them there
while the user reads the message, and marks them with aria-invalid so that the failure is
announced rather than only shown.
aria-describedby, the reason is announced to a screen
reader user rather than only being drawn in red next to boxes they cannot see, and a colour alone is
never the only carrier of the message. A description that is referenced this way is announced when
the focus reaches the code, though, not at the moment the server answers; when the answer arrives
while the focus is elsewhere, put the sentence inside a DescriptionTemplate whose own markup
carries an aria-live="polite", so that it is read out as it changes. It is deliberately
not done for you, since a description holding a countdown or a "resend the code" link would then be
announced on every tick.
Loading
aria-busy, which is what tells a screen reader that the answer is still on its way
instead of leaving the user wondering whether the code was received at all.
Variant
Accent
Size
Style & Class
RTL
API
Every parameter, public member, sub-class and enum this component exposes.
BitOtpInput parameters
| Name | Type | Default value | Description |
|---|---|---|---|
| Accent | BitColor? | null | The accent color of the inputs, applied to the border and the focus ring of the focused input. The error state of the validation still wins over it. |
| AutoFocus | bool | false | If true, the first input left to fill is auto focused on the first render, so a component seeded with a partial code carries on where the typing stopped. A component that starts out disabled cannot take the focus, so it is focused on the first render that finds it enabled instead of losing the auto focus altogether. |
| AutoShift | bool | false | Enables auto shifting the indexes while clearing the inputs using Delete or Backspace, so the remaining characters move one input to the left instead of leaving a hole in the middle of the code. |
| BlurOnFill | bool | false | Removes the focus from the inputs as soon as the code is complete, which is what dismisses the virtual keyboard of a phone once there is nothing left to type. |
| Classes | BitOtpInputClassStyles? | null | Custom CSS classes for different parts of the BitOtpInput. |
| Description | string? | null | The description (helper text) rendered under the inputs, which the group of the inputs references through its aria-describedby so that screen readers announce it along with the name of the group. It is where the sentence that turns a row of empty boxes into a question the user can answer belongs: where the code was sent, how long it is good for, or what a server that rejected it said. |
| DescriptionTemplate | RenderFragment? | null | Custom template for the description (helper text) rendered under the inputs, which takes precedence over the Description. It is referenced the very same way, so a "resend the code" button or a countdown put in here is announced with the group as well. |
| InputAriaLabelFormat | string? | null | The composite format of the aria-label rendered on each input, where {0} is the one based index of the input and {1} is the Length. Set it to localize the position that screen readers announce for each input. The default is "{0} of {1}". |
| InputMode | BitInputMode? | null | Sets the inputmode html attribute of the inputs, which is what decides the virtual keyboard that a phone brings up without changing the element that is rendered or the characters that are accepted. It defaults to the keyboard that matches the Type, so it is only needed to ask for a keyboard the type does not imply, like the telephone keypad (whose keys are larger than the numeric ones on most Android keyboards) for a code of digits. |
| Invalid | bool | false | Paints the inputs with the error state without an EditContext taking part in it, which is what reports a code that the server has rejected ("that code is not correct, try again"): the failure only becomes known once the code has been submitted, so there is nothing for a validator to see. It also marks the inputs with aria-invalid, and a failing validation of an EditContext still shows the very same state on its own. |
| IsLoading | bool | false | Puts the component into the busy state of a code that has been submitted and is being checked, which is the step between the OnFill and the answer that either lets the user through or sets the Invalid. It paints an indeterminate progress bar under the inputs, marks the group with aria-busy so that the wait is announced rather than only shown, and holds the code still the way the ReadOnly does, so that nothing can be typed, pasted or cut over a code whose answer is already on its way. The Clear of the consumer is deliberately not blocked by it. |
| Label | string? | null | Label displayed above the inputs. It is rendered as a real label element bound to the first input and it also names the group of the inputs for assistive technologies. |
| LabelTemplate | RenderFragment? | null | Custom template for the label displayed above the inputs, taking precedence over Label. |
| Length | int | 5 | Length of the OTP or number of the inputs. Values below 1 are clamped to 1, changing it at runtime keeps the characters of the inputs that survive the resize, and a value longer than the inputs can hold loses its extra characters instead of being reported as a value that is not shown. |
| Lowercase | bool | false | Turns every character of the code into its lower case form as it is typed or pasted, the mirror of the Uppercase and applied under the very same rules: before the Pattern is applied, so an expression restricted to lower case letters accepts an upper case keystroke, and to a code that is assigned to the component as much as to one that is typed into it. The Uppercase wins when both are set. |
| Mask | string? | null | The text rendered in place of every filled input, which hides the code without turning the inputs into password inputs, so a masking character of its own (a bullet, an asterisk, an emoji) can be used. The value of the component stays the code that was typed. |
| Merged | bool | false | Glues the inputs of each group together into a single field instead of leaving them standing next to each other: the gaps between them are closed, the rule they share is drawn once, and only the two ends of every group keep their rounding, which is the look of a code printed in a single box. The groups are the ones the Separator makes, so a separator with a SeparatorInterval of 3 renders a six character code as two joined boxes of three. |
| NormalizeDigits | bool | false | Turns the digits of the other numbering systems (the Persian ۰۱۲۳, the Arabic-Indic ٠١٢٣, the full width 0123 and the rest) into their ASCII form as they are typed or pasted, which is what lets a code that arrives in a message written in the language of the user be typed on the keyboard of that language rather than being rejected as if it were not a number at all. The conversion happens before the Pattern is applied and before the Type rejects what is not a digit, and the value of the component is the ASCII form that a server expects. |
| NoSmsAutoFill | bool | false | Disables both the SMS auto fill of the OTP through the WebOTP API of the browser and the one-time-code autofill of the inputs themselves. It also renders the attributes that keep the password manager extensions (1Password, LastPass, Bitwarden, Dashlane) from filling the inputs and from putting their badge over them, since an autocomplete of "off" is a request those extensions deliberately ignore. |
| OnFill | EventCallback<string?> | Callback for when all of the inputs are filled. It is raised once per completed code, so an edit that keeps the very same code does not raise it again. | |
| OnFocusIn | EventCallback<(FocusEventArgs Event, int Index)> | onfocusin event callback for each input, receiving the event and the index of the input that raised it. | |
| OnFocusOut | EventCallback<(FocusEventArgs Event, int Index)> | onfocusout event callback for each input, receiving the event and the index of the input that raised it. | |
| OnInput | EventCallback<(ChangeEventArgs Event, int Index)> | oninput event callback for each input, receiving the event and the index of the input that raised it. | |
| OnInvalid | EventCallback<(string Value, int Index)> | Callback for when what was typed, pasted or auto filled is rejected in full by the Type or the Pattern, so that nothing of it reaches the inputs. It receives the rejected text along with the index of the input that received it, and it is what turns a silent rejection into a visible one. A paste that only loses some of its characters, like a code copied with the dashes in it, is not a rejection and does not raise it. | |
| OnKeyDown | EventCallback<(KeyboardEventArgs Event, int Index)> | onkeydown event callback for each input, receiving the event and the index of the input that raised it. | |
| OnPaste | EventCallback<(ClipboardEventArgs Event, int Index)> | onpaste event callback for each input, receiving the event and the index of the input that raised it. | |
| PasteTransformer | Func<string, string>? | null | A function applied to a chunk of characters that reaches the component in one go (a paste, an SMS auto fill, or a multi character input event) before anything else is done with it, which is what pulls the code out of the text it was copied inside of. The per character filtering of the Type and the Pattern cannot do that on its own for a code of letters, since the letters of the words around it match just as well as the ones of the code: "your code is A1B2C3" would fill the inputs with "YOURCODEI". Returning an empty string rejects the chunk, which raises OnInvalid. It is not applied to a single typed character, and an exception thrown out of it leaves the chunk untouched rather than breaking the input. |
| Pattern | string? | null | A regular expression that every single character of the code has to match, which is what narrows the code down to a set of characters that no input type covers on its own, like upper case letters or hexadecimal digits. Characters that do not match are rejected while typing and dropped while pasting. An unusable expression is ignored rather than breaking the input. |
| Placeholder | string? | null | The hint text rendered in the empty inputs. A string as long as the Length is spread over the inputs one character each, any other value is rendered in every input as is. |
| Reversed | bool | false | Defines whether to render inputs in the opposite direction. The arrow key navigation flips along with it. |
| Separator | string? | null | The text rendered between the inputs, like a dash or a dot, to make a long code easier to read. It is hidden from assistive technologies and never becomes part of the value. |
| SeparatorInterval | int | 1 | The number of inputs of each group that the Separator is rendered between, which is how a long code is split into the chunks it is usually printed in, like 123-456. The default is 1, meaning a separator between every pair of inputs. Values below 1 are treated as 1. |
| SeparatorTemplate | RenderFragment<int>? | null | Custom template rendered between the inputs in place of the Separator text, which is what puts an icon or any other markup between the groups of a code. The context is the zero based index of the input the separator is rendered before, so a template can tell one separator of the row from another. It takes precedence over the Separator. |
| Sequential | bool | false | Keeps the code free of holes: giving the focus to an input that sits after the first empty one, by clicking it or with an arrow key, moves the focus to that first empty input instead, and a chunk of characters that arrives at once (a paste or an auto fill) cannot land past it either, so the code is always filled from its start onwards. Without it a character typed into the middle of an empty row is reported as if it were the first one of the code, since the value is the characters of the inputs joined together and an empty input contributes nothing to it. A complete code is left alone, so any of its characters can still be clicked and corrected. |
| SingleTabStop | bool | false | Turns the whole component into a single stop of the tab order: only the input holding the first character of the code is reachable with the Tab key and the rest are left to the auto advancing focus, the arrow keys and the mouse. Tabbing out of the code then lands on the element after it rather than on its next character. |
| Size | BitSize? | null | The size of the inputs. |
| Styles | BitOtpInputClassStyles? | null | Custom CSS styles for different parts of the BitOtpInput. |
| Type | BitInputType? | null | Type of the inputs, which also decides the virtual keyboard of the mobile browsers. The Number type asks for the numeric keypad and rejects every character that is not a digit, whether it is typed or pasted, without rendering a native number input (which would carry spin buttons and report an empty value for characters like e or -). The Email and the Url types are rendered as text inputs for the same reason, since the constraint validation they carry can never be satisfied by a single character and would keep a plain html form from submitting; only the keyboard they ask for is kept. |
| Uppercase | bool | false | Turns every character of the code into its upper case form as it is typed or pasted, which is what lets a code that is printed in upper case be typed in either case. The conversion happens before the Pattern is applied, so an expression restricted to upper case letters accepts a lower case keystroke instead of rejecting it. |
| Variant | BitVariant? | null | The visual variant of the inputs, which decides how much of the frame around each input is painted: a full fill, only an outline, or just an underline. |
| Vertical | bool | false | Defines whether to render inputs vertically. The arrow key navigation follows the layout. |
BitOtpInput public members
| Name | Type | Default value | Description |
|---|---|---|---|
| InputElements | ElementReference[] | The ElementReferences to the input elements of the BitOtpInput. The inherited InputElement, which every input component carries a single one of, stands for the input holding the first character of the code. | |
| BlurAsync | () => ValueTask | Removes the focus from the input of the BitOtpInput that currently holds it, which is what dismisses the virtual keyboard of a phone. Nothing happens when the focus is somewhere else on the page, so a component that filled itself in the background never takes it away from what the user is doing. | |
| Clear | () => Task | Clears the value of all of the inputs of the BitOtpInput. It does nothing while the component is disabled or read-only. | |
| FocusAsync | (int index = 0) => ValueTask | Gives focus to a specific input element of the BitOtpInput. The index is clamped into the range of the rendered inputs, and calling it before the component has rendered does nothing rather than asking the browser for an element that is not there yet. |
BitInputBase parameters
| Name | Type | Default value | Description |
|---|---|---|---|
| DefaultValue | TValue? | null | The default value of the input to be used in uncontrolled mode (i.e. when the Value is not bound), typically used alongside the OnChange callback. |
| DisplayName | string? | null | Gets or sets the display name for this field. |
| InputHtmlAttributes | IReadOnlyDictionary<string, object>? | null | Gets or sets a collection of additional attributes that will be applied to the created element. |
| Name | string? | null | Gets or sets the name of the element. Allows access by name from the associated form. |
| NoValidate | bool | false | Disables the validation of the input. |
| OnChange | EventCallback<TValue?> | Callback for when the input value changes. | |
| ReadOnly | bool | false | Makes the input read-only. |
| Required | bool | false | Makes the input required. |
| Value | TValue? | null | Gets or sets the value of the input. This should be used with two-way binding. |
BitInputBase public members
| Name | Type | Default value | Description |
|---|---|---|---|
| InputElement | ElementReference | The ElementReference of the input element. | |
| FocusAsync() | () => ValueTask | Gives focus to the input element. | |
| FocusAsync(bool preventScroll) | (bool preventScroll) => ValueTask | Gives focus to the input element. |
BitComponentBase parameters
| Name | Type | Default value | Description |
|---|---|---|---|
| AriaLabel | string? | null | Gets or sets the accessible label for the component, used by assistive technologies. |
| Class | string? | null | Gets or sets the CSS class name(s) to apply to the rendered element. |
| Dir | BitDir? | null | Gets or sets the text directionality for the component's content. |
| ForceAnimation | bool | false | Gets or sets a value indicating whether the component's animations play at their full duration even when reduced motion is requested. |
| HtmlAttributes | Dictionary<string, object> | new Dictionary<string, object>() | Captures additional HTML attributes to be applied to the rendered element, in addition to the component's parameters. |
| Id | string? | null | Gets or sets the unique identifier for the component's root element. |
| IsEnabled | bool | true | Gets or sets a value indicating whether the component is enabled and can respond to user interaction. |
| Style | string? | null | Gets or sets the CSS style string to apply to the rendered element. |
| TabIndex | string? | null | Gets or sets the tab order index for the component when navigating with the keyboard. |
| Visibility | BitVisibility | BitVisibility.Visible | Gets or sets the visibility state (visible, hidden, or collapsed) of the component. |
BitComponentBase public members
| Name | Type | Default value | Description |
|---|---|---|---|
| UniqueId | Guid | Guid.NewGuid() | Gets the readonly unique identifier for the component's root element, assigned when the component instance is constructed. |
| RootElement | ElementReference | Gets the reference to the root HTML element associated with this component. |
BitOtpInputClassStyles properties
| Name | Type | Default value | Description |
|---|---|---|---|
| Root | string? | null | Custom CSS classes/styles for the root element of the otp input. |
| Label | string? | null | Custom CSS classes/styles for the label of the otp input. |
| Description | string? | null | Custom CSS classes/styles for the description (helper text) of the otp input. |
| InputsWrapper | string? | null | Custom CSS classes/styles for the wrapper element of the inputs. |
| Input | string? | null | Custom CSS classes/styles for each input in otp input. |
| Focused | string? | null | Custom CSS classes/styles for the focused input in otp input. |
| Filled | string? | null | Custom CSS classes/styles for each input that already holds a character in otp input. |
| Separator | string? | null | Custom CSS classes/styles for the separator rendered between the inputs of the otp input. |
| Loader | string? | null | Custom CSS classes/styles for the progress bar rendered under the inputs while the otp input is in the loading state. |
BitSize enum
| Name | Value | Description |
|---|---|---|
| Small | 0 | The small size. |
| Medium | 1 | The medium size. |
| Large | 2 | The large size. |
BitInputType enum
| Name | Value | Description |
|---|---|---|
| Text | 0 | The input expects text characters. |
| Password | 1 | The input expects password characters. |
| Number | 2 | The input expects number characters. |
| 3 | The input expects email characters. | |
| Tel | 4 | The input expects tel characters. |
| Url | 5 | The input expects url characters. |
BitInputMode enum
| Name | Value | Description |
|---|---|---|
| None | 0 | No virtual keyboard. For when the page implements its own keyboard input control. |
| Text | 1 | Standard input keyboard for the user's current locale. |
| Decimal | 2 | Fractional numeric input keyboard containing the digits and decimal separator for the user's locale. |
| Numeric | 3 | Numeric input keyboard, but only requires the digits 0–9. |
| Tel | 4 | A telephone keypad input, including the digits 0–9, the asterisk (*), and the pound (#) key. |
| Search | 5 | A virtual keyboard optimized for search input. |
| 6 | A virtual keyboard optimized for entering email addresses. | |
| Url | 7 | A keypad optimized for entering URLs. |
BitVariant enum
| Name | Value | Description |
|---|---|---|
| Fill | 0 | Fill styled variant. |
| Outline | 1 | Outline styled variant. |
| Text | 2 | Text styled variant. |
BitColor enum
| Name | Value | Description |
|---|---|---|
| Primary | 0 | Primary general color. |
| Secondary | 1 | Secondary general color. |
| Tertiary | 2 | Tertiary general color. |
| Info | 3 | Info general color. |
| Success | 4 | Success general color. |
| Warning | 5 | Warning general color. |
| SevereWarning | 6 | SevereWarning general color. |
| Error | 7 | Error general color. |
| PrimaryBackground | 8 | Primary background color. |
| SecondaryBackground | 9 | Secondary background color. |
| TertiaryBackground | 10 | Tertiary background color. |
| PrimaryForeground | 11 | Primary foreground color. |
| SecondaryForeground | 12 | Secondary foreground color. |
| TertiaryForeground | 13 | Tertiary foreground color. |
| PrimaryBorder | 14 | Primary border color. |
| SecondaryBorder | 15 | Secondary border color. |
| TertiaryBorder | 16 | Tertiary border color. |
BitVisibility enum
| Name | Value | Description |
|---|---|---|
| Visible | 0 | The content of the component is visible. |
| Hidden | 1 | The content of the component is hidden, but the space it takes on the page remains (visibility:hidden). |
| Collapsed | 2 | The component is hidden (display:none). |
BitDir enum
| Name | Value | Description |
|---|---|---|
| Ltr | 0 | Ltr (left to right) is to be used for languages that are written from the left to the right (like English). |
| Rtl | 1 | Rtl (right to left) is to be used for languages that are written from the right to the left (like Arabic). |
| Auto | 2 | Auto lets the user agent decide. It uses a basic algorithm as it parses the characters inside the element until it finds a character with a strong directionality, then applies that directionality to the whole element. |
Feedback
Found a mistake, a gap, or something that could be clearer? Every page and every component is one click from its source.