Loving Tina? ⭐️ us on GitHubStar

Docs

Learn

v.Latest
Introduction
Configuring TinaCMS
Querying Content
TinaCloud
Self-Hosting
Advanced

A custom component can be passed and rendered by setting the ui.component property on a field.

This component completely overrides the original component, providing the user with the ability to fully customize the field.

The ui.component Property

This should be set to a valid React component that accepts three props:

  • field: The field definition for the current field.
  • input: The data and callbacks necessary to make an input.
  • meta: Metadata about the field in the form. (e.g. dirty, valid).
  • form: The CMS form, use to retrieve and update other fields from the same collection.
Checkout the react-final-form docs for a more detailed description of the input and meta props.

Preserving Metadata

To keep the same label, description, validation and other meta settings on your custom component, you can make use of the wrapper function wrapFieldsWithMeta.

Form management

The form prop can be used to control form values in other fields.

Any field in the collection can be updated with form.changeform.change(name: string, value: any) => void, using the fields name property.

Any field in the collection can be accessed using form.getFieldState(name: string)?.value, using the fields name property.

Further usage patterns can be inferred from the react-final-form FormAPI docs.

Hidden fields

Setting component.ui to the value "hidden" removes the field from the editor.

The field still exists behind the scenes and can be accessed in other ways.

Examples

Slider component

Here is a custom slider component that can be used for adjusting image saturation.

A basic slider custom component

{
label: "Image Saturation",
name: "saturation",
type: "number",
description: "My custom saturation field",
ui: {
parse: (val) => Number(val),
component: wrapFieldsWithMeta(({ field, input, meta }) => {
return (
<div>
<input
name="saturation"
id="saturation"
type="range"
min="0"
max="10"
step=".1"
// Passes props.input.onChange
{...input}
/>
<br />
Value: {input.value}
</div>
)
})
}
}

Image component with hidden meta-fields

NextJS and other frameworks are able to improve performance if image widths and heights are known ahead of time.

TinaCMS can be used to determine and store this on upload.

We can pass along the existing TinaCMS Image component, and intercept changes to it with a useEffect that updates our width and height fields.

import { ImageField } from 'tinacms';
const CustomImageField = (props) => {
const loadImage = async (url) => {
const img = new Image();
img.src = url;
await img.decode();
return img;
};
const { form, input } = props;
useEffect(() => {
loadImage(input.value).then((img) => {
form.change("imgWidth", img.naturalWidth);
form.change("imgHeight", img.naturalHeight);
});
}, [form, input]);
return ImageField(props);
};

In the content model we also need two additional fields for height and width, which are set to "hidden" so they don't appear in the editor.

{
type: 'image',
label: 'Hero image',
name: 'imgSrc',
ui: {
component: CustomImageField,
},
},
{
type: "number",
name: "imgWidth",
ui: {
component: "hidden",
},
},
{
type: "number",
name: "imgHeight",
ui: {
component: "hidden",
},
}

This shows up as a singular Image input in the editor, but 3 values will be saved on each update (image source, height and width).

imgSrc: /uploads/llama - 2.avif
imgWidth: 987
imgHeight: 1480

Conditional component

Conditional fields can be created by checking values of other form fields.

With this approach, a hidden conditional field will hold whatever value was saved to it before it was hidden.

{
type: "boolean",
name: "imageEnabled",
label: "Image Enabled",
},
{
type: 'image',
label: 'Hero image',
name: 'imgSrc',
ui: {
component: (props) => {
const { form } = props;
return form.getFieldState("imageEnabled")?.value ?
ImageField(props) : null;
},
},
},

Icon-picker component (video)

The below tutorial includes a segment on creating a custom Icon selector using a custom field component.

Last Edited: March 26, 2025