Skip to main content

Sections

Adding section: 'SectionName' to one or more field overrides causes UniForm to group those fields under a shared section wrapper. Fields without a section render first (or in order order), then sections render in first-encounter order.

<AutoForm
form={myForm}
fields={{
firstName: { section: 'Personal' },
lastName: { section: 'Personal' },
email: { section: 'Contact' },
phone: { section: 'Contact' },
}}
/>

Default section wrapper

The default sectionWrapper renders a <fieldset> with a <legend> containing the section title. Override it via the layout.sectionWrapper slot.

Custom section card wrapper

Replace the wrapper for all sections at once with layout.sectionWrapper:

const SectionCard = ({ title, children, className }) => (
<div className={className}>
<h3>{title}</h3>
<div>{children}</div>
</div>
)

<AutoForm layout={{ sectionWrapper: SectionCard }} ... />

Per-section styling

Use layout.sections to apply a className or swap the wrapper component for individual sections, without touching the others:

<AutoForm
layout={{
sections: {
Personal: { className: 'bg-blue-50 p-4 rounded' },
Address: { className: 'bg-green-50 p-4 rounded' },
},
}}
...
/>

Per-section component override

For complete control over a single section, provide a component:

const HighlightedSection = ({ title, children, className }) => (
<div className={`highlighted-section ${className ?? ''}`}>
<h2>{title}</h2>
{children}
</div>
)

<AutoForm
layout={{
sections: {
Personal: { component: HighlightedSection, className: 'vip' },
Address: { className: 'secondary' },
},
}}
...
/>

When component is provided it replaces the sectionWrapper for that section only. The className is forwarded to both the per-section component and the global sectionWrapper.

With createAutoForm

Factory-level and instance-level sections are merged — instance wins on conflicts:

const AppForm = createAutoForm({
layout: {
sections: {
Personal: { className: 'card' },
},
},
})

// Inherits factory 'Personal' class; adds 'Address' class on top
<AppForm
layout={{ sections: { Address: { className: 'card card--secondary' } } }}
...
/>

SectionConfig type

type SectionConfig = {
/** CSS class name forwarded to the section wrapper. */
className?: string
/** Replace the section wrapper component for this section only. */
component?: React.ComponentType<{
children: React.ReactNode
title: string
className?: string
}>
}

Live Example

Live Editor
const SectionCard = ({ title, children }) => (
  <div
    style={{
      border: '1px solid var(--ifm-color-emphasis-300)',
      borderRadius: 10,
      marginBottom: '1rem',
      overflow: 'hidden',
    }}
  >
    <div
      style={{
        background: 'var(--ifm-color-emphasis-200)',
        borderBottom: '1px solid var(--ifm-color-emphasis-300)',
        padding: '0.6rem 1rem',
        fontWeight: 600,
        fontSize: 13,
        color: 'var(--ifm-font-color-base)',
        textTransform: 'uppercase',
        letterSpacing: '0.05em',
      }}
    >
      {title}
    </div>
    <div style={{ padding: '1rem' }}>{children}</div>
  </div>
)

const schema = z.object({
  firstName: z.string().min(1, 'Required'),
  lastName: z.string().min(1, 'Required'),
  email: z.string().email(),
  phone: z.string().optional(),
  addressLine1: z.string().optional(),
  city: z.string().optional(),
  country: z.enum(['US', 'CA', 'GB', 'AU']).optional(),
  newsletter: z.boolean(),
  smsAlerts: z.boolean(),
})

const profileForm = createForm(schema)

function App() {
  const [result, setResult] = React.useState(null)
  return (
    <div style={{ fontFamily: 'system-ui', maxWidth: 480 }}>
      <AutoForm
        form={profileForm}
        layout={{ sectionWrapper: SectionCard }}
        fields={{
          firstName: { label: 'First Name', span: 6, section: 'Personal' },
          lastName: { label: 'Last Name', span: 6, section: 'Personal' },
          email: { section: 'Contact' },
          phone: { section: 'Contact' },
          addressLine1: { label: 'Address', section: 'Address' },
          city: { section: 'Address' },
          country: { section: 'Address' },
          newsletter: { label: 'Email newsletter', section: 'Notifications' },
          smsAlerts: { label: 'SMS alerts', section: 'Notifications' },
        }}
        defaultValues={{ newsletter: false, smsAlerts: false }}
        onSubmit={(v) => setResult(v)}
      />
      {result && (
        <pre
          style={{
            marginTop: '1rem',
            background: 'var(--ifm-color-emphasis-200)',
            padding: '1rem',
            borderRadius: 6,
          }}
        >
          {JSON.stringify(result, null, 2)}
        </pre>
      )}
    </div>
  )
}

render(<App />)
Result
Loading...