UNPKG

@dnb/eufemia

Version:

DNB Eufemia Design System UI Library

316 lines (254 loc) 10.3 kB
--- title: 'Create your own component' version: 10.104.0 generatedAt: 2026-04-17T18:46:12.716Z checksum: 090b7d977ba4be5e2c4c04d199a30a4048416c59f443a56985df2f80629d9c40 --- # 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](/uilib/extensions/forms/Value/#base-components) and base [field components](/uilib/extensions/forms/base-fields/). **Table of Contents** - [Value components](#value-components) - [Field components](#field-components) - [Layout](#layout) - [Localization and translations](#localization-and-translations) ## Value components For creating a `Value.*` component you can use [ValueBlock](/uilib/extensions/forms/create-component/ValueBlock/) and [useValueProps](/uilib/extensions/forms/create-component/useValueProps/): ```tsx 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](/uilib/extensions/forms/create-component/ValueBlock/) provides a standardized way to display labels and other surrounding elements in a consistent manner. ```tsx const MyValue = ({ value, ...props }) => { return ( <ValueBlock {...props}> <NumberFormat currency>{value}</NumberFormat> </ValueBlock> ) } render(<MyValue label="Label" value={1234} />) ``` 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](/uilib/extensions/forms/create-component/FieldBlock/) and [useFieldProps](/uilib/extensions/forms/create-component/useFieldProps/): ```tsx 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: ```tsx const MyField = (props) => { const fromInput = React.useCallback(({ value }) => value, []) const preparedProps = { label: 'What is the secret of this field?', fromInput, onChangeValidator: (value) => { if (value === 'secret') { return new Error('Do not reveal the secret!') } }, ...props, } const { id, value, label, handleChange, handleFocus, handleBlur } = useFieldProps(preparedProps) return ( <FieldBlock forId={id} label={label}> <Input id={id} value={value} on_change={handleChange} on_focus={handleFocus} on_blur={handleBlur} /> </FieldBlock> ) } render( <MyField onChange={(value) => console.log('onChange', value)} required /> ) ``` ### Further customization You can customize the behavior of the field component. For example, you can add a custom error message: ```tsx import { useFieldProps } from '@dnb/eufemia/extensions/forms' useFieldProps({ errorMessages: { 'Field.errorRequired': 'Show this when "required" fails.', }, }) ``` or a custom `required` property validation function: ```tsx 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](/uilib/extensions/forms/create-component/useFieldProps/) or the [FieldBlock](/uilib/extensions/forms/create-component/FieldBlock/) documentation. ### More details This example shows a custom component. The [useFieldProps](/uilib/extensions/forms/create-component/useFieldProps/) hook receives the properties and adds extra properties to standardize field behavior. These include `handleFocus`, `handleChange`, and `handleBlur` functions. Even if the field components have external callbacks like "onChange", these will not be altered. The "handle" variants simplify 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](/uilib/extensions/forms/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](/uilib/extensions/forms/create-component/useFieldProps/) and other helper functions to make the component work exactly as you want. ### Your own validation If you need custom validation that cannot 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](/uilib/extensions/forms/create-component/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 from the usual fields, you can drop [FieldBlock](/uilib/extensions/forms/create-component/FieldBlock/) and display surrounding elements in other ways – but still get all the help of a data flow logic, such as [useFieldProps](/uilib/extensions/forms/create-component/useFieldProps/) offers. Here follows an example that retrieves data from a surrounding DataContext, and creates a composite field based on other components from Eufemia: ```tsx const MyComposedField = (props) => { const { id, value, hasError, handleChange, handleFocus, handleBlur } = useFieldProps({ path: '/birthYear', }) const handleBirthYearChange = React.useCallback( (sliderData) => { handleChange(sliderData.value) }, [handleChange] ) return ( <FieldBlock id={id} label={props.label ?? 'Name and age'}> <Flex.Horizontal> <Field.Name.First path="/firstName" width="medium" minLength={2} /> <Field.Name.Last path="/lastName" width="medium" required /> <FieldBlock width="large"> <Slider min={1900} max={new Date().getFullYear()} step={1} label="Birth year" label_direction="vertical" value={parseFloat(String(value))} on_change={handleBirthYearChange} on_drag_start={handleFocus} on_drag_end={handleBlur} status={hasError} tooltip /> </FieldBlock> </Flex.Horizontal> </FieldBlock> ) } const data = { firstName: 'John', birthYear: 2000, } render( <DataContext.Provider defaultData={data} onChange={(data) => console.log('onChange', data)} > <MyComposedField label="My custom label" /> </DataContext.Provider> ) ``` ## Layout When building your custom form components, preferably use the [Layout](/uilib/layout) component. ### Width definitions These are the official sizes you can use when [creating your own form fields](/uilib/extensions/forms/create-component/). ```css :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](/uilib/extensions/forms/create-component/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](/uilib/extensions/forms/Form/useTranslation/) hook to use existing translations or extend it with your custom field localization: ```tsx 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> ) } ```