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:

RadioGroup

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
<TextInput
required
label="Input label"
description="Put additional information about input here"
error="Display validation error"
/>
Please enter your credit card information, we need some money
Your credit card expired
<InputWrapper
id="input-demo"
required
label="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 or inputClassName to apply styles to input element (style and className 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:

<Input
component="button"
onClick={() => setDropdownOpened(true)}
inputStyle={{ cursor: 'pointer' }}
elementRef={controlRef}
{...others}
>
{/*
Since Input is rendered as a button
we 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 generated
const 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 prop
const colors = data.map((color) => (
<ColorSwatch
key={color}
// make color swatch interactive, focus styles from theme are already applied
component="button"
color={color}
onClick={() => handleChange(color)}
style={{ cursor: 'pointer' }}
/>
));
const dropdown = (
<Paper
// predefined shadow and padding from theme.shadows and theme.spacing
shadow="md"
padding="md"
// get element ref for focus trap and click outside
elementRef={dropdownRef}
// Close dropdown when user presses escape
// since focus is trapped inside we do not need to pollute window with this event
onKeyDownCapture={(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:

<Transition
transition="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.