Skip to content
Playground Form Builder

UI customization

In addition to the predefined UI options, each widget and theme component extends the UiOptions interface with at least one property that allows customization.

For example, the textWidget from @sjsf/basic-theme adds the property text?: HTMLInputAttributes. By specifying it in the UI options, you can control the widget’s attributes - even reactively.

<script lang="ts">
import { Content, createForm, setFormContext } from '@sjsf/form';
import * as defaults from "@/lib/form/defaults";
let width = $state(50)
const form = createForm({
...defaults,
schema: {type: 'string' },
uiSchema: {
'ui:options': {
text: {
get style() {
return `flex-grow: 0; width: ${width}%`
}
}
}
}
})
setFormContext(form)
</script>
<input style="width: 100%;" type="range" bind:value={width} />
<Content />

Check your theme page for an extended list of UI options:

By specifying action?: FieldAction<SchemaValue | undefined> in the UI options, your snippet will be rendered on the right side of the field header.

<script lang="ts">
import type { Ref } from '@sjsf/form/lib/svelte.svelte';
import {
Content,
createForm,
setFormContext,
type Config,
type FieldErrors,
type FormState
} from '@sjsf/form';
import * as defaults from "@/lib/form/defaults";
const form = createForm({
...defaults,
schema: { type: "number" },
uiSchema: {
'ui:options': {
title: "Number",
action: randomInt
}
}
})
setFormContext(form)
</script>
{#snippet randomInt(_ctx: FormState<unknown>, _config: Config, valueRef: Ref<unknown>, _errors: FieldErrors)}
<button type="button" onclick={() => {
valueRef.current = Math.floor(Math.random() * 100)
}}>
Random
</button>
{/snippet}
<Content />

You can also use the prebuilt actions available in the @sjsf/form/fields/actions/* module:

  • clear-edit.svelte toggles between undefined and default value
  • clear.svelte sets value to undefined
  • display-primitive-value.svelte displays current value

You can use UI options registry to ensure serializability of the UI schema.

To do this, you need to extend the UiOptionsRegistry interface and replace the non-serializable value with registry:${key} in UiOptions. Example:

import { createForm } from "@sjsf/form"
import type { ItemTitle } from "@sjsf/form/fields/array/model";
declare module "@sjsf/form" {
interface UiOptionsRegistry {
myItemTitle: ItemTitle
}
}
const form = createForm({
...,
uiSchema: {
"ui:options": {
itemTitle: "registry:myItemTitle"
}
},
uiOptionsRegistry: {
myItemTitle: (title, index) => `${title} [${index}]`
}
})

This property allows you to specify UI option for all components or compute it dynamically.

<script lang="ts">
import { chain, fromFactories, fromRecord } from '@sjsf/form/lib/resolver';
import { Content, createForm, setFormContext, type Config } from '@sjsf/form';
import * as defaults from "@/lib/form/defaults";
const form = createForm({
...defaults,
schema: {
type: 'object',
properties: {
foo: {
type: "string"
},
bar: {
type: "number"
}
}
},
extraUiOptions: chain(
fromRecord({
labelAttributes: {
style: 'color: red'
}
}),
fromFactories({
help: (config: Config) => `${config.path.at(-1)} help`
})
)
})
setFormContext(form)
</script>
<Content />