Skip to content

Create your own component

Eufemia Forms contains helper fields and tools so you can declaratively create interactive form components that flawlessly integrates between existing data and your custom form components.

By using the building blocks for value and field components, you save development time, and at the same time ensure that local, custom components work similarly, and fit into the setup with the standardized base value components and base field components.

Table of Contents

Value components

For creating a Value.* component you can use ValueBlock and useValueProps:

import { ValueBlock, useValueProps } from '@dnb/eufemia/extensions/forms'
const MyValue = (props) => {
const { value, ...rest } = useValueProps(props)
return <ValueBlock {...rest}>{value}</ValueBlock>
}
render(<MyValue path="/dataSelector" />)

ValueBlock provides a standardized way to display labels and other surrounding elements in a consistent manner.

Label

The useValueProps provides a standardized way to handle data flow in a consistent manner.

The FieldBlock provides a standardized way to display a label and other surrounding elements in a consistent manner.

Field components

For creating a Field.* component, you can use FieldBlock and useFieldProps:

import { FieldBlock, useFieldProps } from '@dnb/eufemia/extensions/forms'
const MyField = (props) => {
const {
id,
value,
handleChange,
handleFocus,
handleBlur,
htmlAttributes,
} = useFieldProps(props)
return (
<FieldBlock
forId={id}
id={id} // If your field component does not support the HTML "for" attribute, you can use the "id" property instead of "forId".
>
<input
id={id}
value={value}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
{...htmlAttributes}
/>
</FieldBlock>
)
}
render(<MyField label="Label text" path="/dataSelector" />)

The useFieldProps provides a standardized way to handle data flow, validation and error messages in a consistent manner.

The FieldBlock provides a standardized way to display labels, error messages and other surrounding elements in a consistent manner.

Here is a working example with code you can edit in the playground:

Further customization

You can customize the behavior of the field component. For example, you can add a custom error message:

import { useFieldProps } from '@dnb/eufemia/extensions/forms'
useFieldProps({
errorMessages: {
'Field.errorRequired': 'Show this when "required" fails.',
},
})

or a custom required property validation function:

import { FormError } from '@dnb/eufemia/extensions/forms'
const validateRequired = (value, { emptyValue, required, isChanged }) => {
if (required && value === emptyValue) {
return new FormError('Field.errorRequired')
}
}
useFieldProps({ validateRequired })

For more information about all the available parameters, see the useFieldProps or the FieldBlock documentation.

More details

This example shows a custom component. The useFieldProps hook receives the properties and adds extra properties to standardize field behavior. These includes handleFocus, handleChange, and handleBlur functions. Even if the field components has external callbacks like "onChange", these won't be altered. The "handle" variants simplifies your code.

The example explained

Using these two form helpers in your field component triggers several automatic processes. These include timely validation checks, syncing value changes with the DataContext, coordinating error messages across multiple fields, and preventing premature error displays while the user is editing the field.

Keep in mind, you can customize the behavior of useFieldProps and other helper functions to make the component work exactly as you want.

Your own validation

If you need custom validation that can't use the built-in JSON Schema or a derivative validator (like in the example above), you can create your own logic. Then, pass the result as an error property to FieldBlock. All direct properties override standard handling, giving you full control over your component.

Customized even further

If you need something that looks even more different than the usual fields, you can drop FieldBlock and display surrounding elements in other ways – but still get all the help of a data flow logic, such as useFieldProps offers.

Here follows an example that retrieves data from a surrounding DataContext, and creates a composite field based on other components from Eufemia:

Layout

When building your custom form components, preferably use the Layout component.

Width definitions

These are the official sizes you can use when creating your own form fields.

:root {
/* Field */
--forms-field-width--small: 5rem;
--forms-field-width--medium: 11rem;
--forms-field-width--large: 21rem;
--forms-field-label-max-width--large: 60ch;
/* Value */
--forms-value-width--small: 30ch;
--forms-value-width--medium: 40ch;
--forms-value-width--large: 60ch;
--forms-value-label-max-width--large: 60ch;
}

You can also use a FieldBlock and provide a width property with a value of either small, medium or large and use it as a sized wrapper.

Localization and translations

You can use the Form.useTranslation hook to use existing translations or extend it with your custom field localization:

import {
Form,
FieldBlock,
useFieldProps,
FieldProps,
} from '@dnb/eufemia/extensions/forms'
const myFieldTranslations = {
'en-GB': {
MyField: {
label: 'My field',
requiredMessage: 'Custom required message',
},
},
'nb-NO': {
MyField: {
label: 'Mitt felt',
requiredMessage: 'Obligatorisk felt melding',
},
},
}
type Translation =
(typeof myFieldTranslations)[keyof typeof myFieldTranslations]
type MyFieldValue = string
const MyField = (props: FieldProps<MyFieldValue>) => {
const translations = Form.useTranslation<Translation>(
myFieldTranslations,
)
const { label, requiredMessage } = translations.MyField
const preparedProps = {
label,
errorMessages: {
'Field.errorRequired': requiredMessage,
},
...props,
}
const { id, value, handleChange, handleFocus, handleBlur } =
useFieldProps(preparedProps)
return (
<FieldBlock<MyFieldValue> forId={id}>
<input
id={id}
value={value}
onChange={handleChange}
onFocus={handleFocus}
onBlur={handleBlur}
/>
</FieldBlock>
)
}