Mantine inputs
Mantine comes with essential set of inputs to help you build forms. Most of the inputs share the same props, this guide will help you learn about patters which should be used with inputs to ensure nest accessibility and usability.
Input types
Mantine inputs can be divided into two categories: with and without InputWrapper.
Inputs with InputWrapper:
Inputs without InputWrapper:
InputWrapper component
All inputs with InputWrapper support these optional props:
- label – input label, bind to input element with
for
- required – displays required asterisk at the end of the label
- description – displays description after label, put additional information here, for example, password requirements
- error – any react node to displays error message after input and highlight input border with red color, or
true
just for input border highlight
<TextInputrequiredlabel="Input label"description="Put additional information about input here"error="Display validation error"/>
<InputWrapperid="input-demo"requiredlabel="Credit card information"description="Please enter your credit card information, we need some money"error="Your credit card expired"><Input id="input-demo" placeholder="Your email" /></InputWrapper>
Input component
Mantine exports utility Input component. This component is a base for
Since all these inputs share the same parent Input component they support props:
- icon – renders icon on the left
- radius – applies border-radius from theme
- disabled – disables any interactions with input
- variant – choose between default, filled and unstyled variant
- rightSection related props (not supported by Select, NumberInput and PasswordInput) to add extra content on the right
Go to Input component docs to see examples of all above props usage.
Accessibility
Please follow accessibility guidelines for each input individually.
Most of the inputs have shared rule about label –
provide aria-label
in case you use component without label for screen reader support:
<TextInput label="My input" />; // -> ok, input and label is connected<TextInput />; // not ok, input is not labeled<TextInput aria-label="My input" />; // -> ok, label is not visible but will be announced by screen reader
Building custom inputs
@mantine/core and @mantine/hooks come with all utilities that you need to build custom inputs. These examples with provide a reference on how to enhance existing components with extra logic to fit your needs and how to use mantine packages to create completely new accessible inputs based on Input and InputWrapper components.
JsonInput
JsonInput uses autosize variant of Textarea component, which accepts json, validates and formats it.
Key moments:
- Use
inputStyle
orinputClassName
to apply styles to input element (style
andclassName
props will go to InputWrapper) onChange
,onFocus
,onBlur
and all other input related props go directly to input element- Use
typeof Textarea
to get Textarea component props in TypeScript
ColorInput
ColorInput is a custom input built with Input and InputWrapper components.
Input
For this input we will use Input as button, as we do not want to allow any free user input. We will also grab button ref for future focus management:
<Inputcomponent="button"onClick={() => setDropdownOpened(true)}inputStyle={{ cursor: 'pointer' }}elementRef={controlRef}{...others}>{/*Since Input is rendered as a buttonwe can use children to display current value or placeholder*/}<div style={{ display: 'flex', alignItems: 'center' }}><ColorSwatch color={value} size={20} style={{ marginRight: 10 }} /><Text size="sm" transform="uppercase">{value}</Text></div></Input>
InputWrapper
To give ColorInput component the same label, description and error props as in other Mantine inputs, we will wrap it with InputWrapper and ensure that label is connected to input with use-id hook:
// if input receives id from props, this id will be used,// otherwise random id will be generatedconst uuid = useId(id);// We just set InputWrapper props from ColorInput props// It's not a rocket science as you see<InputWrapper required={required} id={uuid} label={label} error={error} description={description}><Input id={uuid} /* other input props */ /></InputWrapper>;
Create a dropdown
Dropdown is built with Paper and ColorSwatch components.
// Colors generated from data propconst colors = data.map((color) => (<ColorSwatchkey={color}// make color swatch interactive, focus styles from theme are already appliedcomponent="button"color={color}onClick={() => handleChange(color)}style={{ cursor: 'pointer' }}/>));const dropdown = (<Paper// predefined shadow and padding from theme.shadows and theme.spacingshadow="md"padding="md"// get element ref for focus trap and click outsideelementRef={dropdownRef}// Close dropdown when user presses escape// since focus is trapped inside we do not need to pollute window with this eventonKeyDownCapture={(event) => {if (event.nativeEvent.code === 'Escape') {closeDropdown();}}}><Group position="center">{colors}</Group></Paper>);
Click outside and focus trap
When dropdown is opened usually it is a good idea to trap focus inside and close it with outside clicks. To implement this use use-click-outside and use-focus-trap. Both hooks return ref that should be passed to dropdown, to combine them use use-merged-ref hook:
const focusTrapRef = useFocusTrap();const clickOutsideRef = useClickOutside(closeDropdown);const dropdownRef = useMergedRef(focusTrapRef, clickOutsideRef);// on dropdown component<Paper elementRef={dropdownRef} /* ...other dropdown props */ />;
Add transitions
To animate dropdown presence we will use Transition
component, it has some premade transitions, for this example skew-up
will do the job:
<Transitiontransition="skew-up"duration={250}mounted={dropdownOpened}onExited={() => setTimeout(() => controlRef.current.focus(), 10)}>{(transitionStyles) => <Paper style={transitionStyles} /* ...other dropdown props */ />}</Transition>
When dropdownOpened
is false, dropdown will not be mounted to the dom
– focus trap will have no effect and click outside events will not be registered.
When dropdown transition is finished we move focus back to control with onExit
callback.