Redpanda UIRedpanda UI

Auto Form

Generate registry-native forms from Buf protobuf descriptors.

Made by malinskibeniamin

Installation

What is AutoForm

AutoForm is the schema-driven form platform for the registry.

It accepts Buf-generated protobuf descriptors or existing provider instances and renders a complete form with validation, field rendering, and payload management.

AutoForm v2 adds:

  • Simple / Advanced / JSON modes
  • an opt-in payload preview panel
  • stable testId-driven data-testid hooks for unit, integration, and e2e coverage
  • richer registry-native controls like InputGroup, Calendar, MultiSelect, RadioGroup, Toggle, and KeyValueField
  • proto UI metadata plus UI CEL rules
  • optional stepper flows
  • payloadBuilder and payloadParser hooks for backend-shaped previews that still round-trip through JSON mode
Loading component...

Modes and summary

Use modes to switch between:

  • simple — only the minimum required surface
  • advanced — the full form
  • json — an editable payload editor

Enable showSummary to render a payload preview summary rail on wider screens. When space gets tight, it automatically drops below the form instead of squeezing the controls.

Testing hooks

AutoForm accepts a top-level testId prop and uses it as the form-wide prefix for stable data-testid attributes.

That gives you predictable selectors like:

  • project-launch-form
  • project-launch-form-field-project-name
  • project-launch-form-field-project-name-control
  • project-launch-form-tab-json
  • project-launch-form-summary

If you omit testId, AutoForm falls back to autoform, but for multi-form pages you should set an explicit prefix.

Field-type coverage

AutoForm auto-detects common patterns from proto field metadata:

  • emails and URLs use InputGroup
  • long text uses Textarea
  • consent-style booleans use Checkbox
  • regular booleans use Switch
  • secret-like names use password inputs
  • repeated enums use MultiSelect
  • larger enums promote to Combobox
  • bounded numeric fields use a slider + number input

Use fieldConfig.fieldType when you want an explicit control override (e.g., radio, toggle, currency, json, password, slider).

Protobuf usage

AutoForm accepts a generated Buf descriptor, not raw .proto text at runtime.

'use client';

import { AutoForm } from '@/components/auto-form';
import '@/lib/protobuf-provider/auto-form-example-annotations';
import {
  type AutoFormExample,
  AutoFormExampleSchema,
} from '@/lib/protobuf-provider/gen/auto-form-example_pb';

export function Example() {
  return (
    <AutoForm<AutoFormExample>
      defaultMode="simple"
      modes={['simple', 'advanced', 'json']}
      schema={AutoFormExampleSchema}
      showSummary
      withSubmit
    />
  );
}
Loading component...

Proto UI metadata and CEL

The easiest way to think about this is:

  • Protovalidate CEL handles validation
  • AutoForm UI CEL handles visibility, disabled state, and step progression

So the proto schema declares the UI intent, protobuf-provider normalizes it, and AutoForm applies it at runtime.

The focused teaching fixture lives in:

  • packages/registry/src/lib/protobuf-provider/proto/auto-form-example.proto
  • packages/registry/src/lib/protobuf-provider/proto/auto_form_ui.proto

Copy-paste proto example

message AutoFormUiMetadataExample {
  option (redpanda.ui.registry.autoform.v1.message_ui).steps = {
    id: "cluster"
    title: "Cluster"
    field_paths: "clusterName"
    field_paths: "provider"
    field_paths: "region"
    field_paths: "enableSupportMode"
  };
  option (redpanda.ui.registry.autoform.v1.message_ui).steps = {
    id: "support"
    title: "Support"
    field_paths: "supportTier"
    field_paths: "maintenanceWindow"
    field_paths: "escalationReason"
    field_paths: "supportContact"
    visible_when: {
      expression: "form.enableSupportMode"
    }
    complete_when: {
      expression: "!form.enableSupportMode || (form.supportTier != 0 && form.supportContact.case != '')"
      message: "Choose a support tier and support contact before continuing."
    }
  };

  UiDemoProvider provider = 2 [
    (redpanda.ui.registry.autoform.v1.field_ui) = {
      control: CONTROL_TYPE_RADIO_GROUP
    }
  ];

  string region = 3 [
    (redpanda.ui.registry.autoform.v1.field_ui) = {
      disabled_when: {
        expression: "form.provider == 0"
      }
    }
  ];

  bool enable_support_mode = 4 [
    (redpanda.ui.registry.autoform.v1.field_ui) = {
      control: CONTROL_TYPE_TOGGLE
    }
  ];

  string escalation_reason = 7 [
    (redpanda.ui.registry.autoform.v1.field_ui) = {
      control: CONTROL_TYPE_TEXTAREA,
      visible_when: {
        expression: "form.supportTier == 3"
      }
    }
  ];

  oneof support_contact {
    option (redpanda.ui.registry.autoform.v1.oneof_ui) = {
      help: "This oneof only appears after a support tier is chosen."
      visible_when: {
        expression: "form.enableSupportMode && form.supportTier != 0"
      }
    };

    string support_email = 8;
    string slack_channel = 9;
    bool no_follow_up = 10;
  }
}

What this gives you:

  • the Support step only appears when support mode is enabled
  • region stays disabled until a provider is selected
  • escalationReason only appears for the highest support tier
  • the supportContact oneof appears only after a support tier is chosen
  • Next stays blocked until the complete_when rule passes

Generated descriptor usage

'use client';

import { AutoForm } from '@/components/auto-form';
import '@/lib/protobuf-provider/auto-form-example-annotations';
import {
  type AutoFormUiMetadataExample,
  AutoFormUiMetadataExampleSchema,
} from '@/lib/protobuf-provider/gen/auto-form-example_pb';

export function Example() {
  return (
    <AutoForm<AutoFormUiMetadataExample>
      modes={['advanced', 'json']}
      schema={AutoFormUiMetadataExampleSchema}
      showSummary
      stepper
      testId="proto-ui-metadata-form"
      withSubmit
    />
  );
}

Interactive preview

Loading component...

Compact reference

OptionUse it for
message_ui.stepsOrdered step definitions, titles, descriptions, field paths, step visibility, and step completion rules
field_ui.controlPreferred AutoForm control like radio, toggle, textarea, timestamp, or JSON
field_ui.placeholder / example / helpUser guidance that stays close to the generated field
field_ui.visible_whenHide or show a field from current form state
field_ui.disabled_whenKeep a field visible but temporarily locked
field_ui.stepLightweight field-to-step assignment when you do not want message-level step definitions
oneof_ui.helpOneof-level helper copy for grouped selections
oneof_ui.visible_when / disabled_whenConditional rendering or disabling for the whole oneof
oneof_ui.stepLightweight oneof-to-step assignment

CEL authoring notes

  • form is the full current form payload
  • this is the current field value or step-local payload
  • keep UI CEL short and predictable
  • use UI CEL for rendering and progression, not backend validation

Practical tips

  • Prefer proto metadata over bespoke React conditionals when the rule belongs to the schema.
  • Set an explicit testId so unit, integration, and e2e selectors stay stable.
  • Start from AutoFormUiMetadataExample when you need a small, copyable template for a new protobuf-backed form.

Stepper flows

Stepper support is optional.

  • use stepper to enable proto-declared steps
  • or pass steps directly for manual flows

If both are provided, the explicit steps prop wins.

<AutoForm
  schema={AutoFormExampleSchema}
  stepper
  withSubmit
/>
Loading component...

Payload preview hooks

Use payloadBuilder when the payload sent to your backend differs from raw form state.

Use renderSummary when you want the side panel to show something other than the default payload JSON preview.

If your payload shape differs from raw form state and you still want JSON-mode edits to sync back into the form, pair payloadBuilder with payloadParser.

<AutoForm
  modes={['advanced', 'json']}
  payloadBuilder={(values) => ({
    request: {
      owner: { email: values.ownerEmail },
      rollout: {
        mode: values.enableDryRun ? 'dry-run' : 'live',
        region: values.targetRegion,
      },
      team: values.teamName,
    },
  })}
  payloadParser={(payload) => fromRequestPayload(payload)}
  schema={schema}
  showSummary
  withSubmit
/>

Validation and error handling

For protobuf forms, AutoForm uses:

  • @bufbuild/protobuf for reflection and message generation
  • @bufbuild/protovalidate for validation
  • built-in UI CEL evaluation for visibility and step rules
  • a built-in createProtoResolver() bridge for React Hook Form

That gives you:

  • field-level validation messages
  • required oneof feedback
  • repeated/map validation
  • message-level CEL errors surfaced in a top-level alert
Loading component...

Generated proto workflow

AutoForm expects the descriptor layer to be generated ahead of time.

  1. Write your .proto
  2. Generate protobuf-es output with Buf
  3. Optionally register comment annotations if you want docs-style descriptions from proto comments
  4. Import the generated *Schema descriptor into AutoForm

In this repo, generation is wired through:

  • packages/registry/buf.yaml
  • packages/registry/buf.gen.yaml
  • packages/registry/src/scripts/generate-proto-annotations.mts

Regenerate with:

bun --filter=@redpanda/registry run proto:generate

API

schema

Accepts one of:

  • a direct Buf DescMessage
  • an existing provider instance

fieldConfig

Schema-agnostic UI overrides keyed by field path.

modes

Controls which tabs are available.

Default: ['advanced']

defaultMode

Controls the initial tab.

showSummary

Shows the live summary panel for form modes.

renderSummary

Optional custom renderer for the summary panel.

stepper

Opt-in switch for proto-defined stepper flows.

steps

Manual step definitions for custom flows.

payloadBuilder

Transforms current form values into the payload shown in the JSON tab and summary panel.

payloadParser

Optional inverse mapper for editable JSON mode when the payload shape differs from the form shape.

resolver

Optional override if you need custom resolver behavior.

AutoForm will automatically use createProtoResolver() for direct protobuf descriptors.

Supported protobuf field coverage

Proto shapeAutoForm UI
stringtext / email / URL / password / textarea / currency presentations
numeric scalarsnumber input or slider + number input
64-bit integersstring-backed integer input
booltri-state select / checkbox / switch / toggle
bytesbytes editor (optional to hide in demos if that flow is too noisy)
enumsradio / select / combobox
nested messagesobject section or JSON override
repeated fieldsarray editor / multiselect
mapskey/value editor
oneofcase selector + active field editor
google.protobuf.Timestampcalendar + time controls
google.protobuf.Durationduration text input
google.protobuf.FieldMaskpaths editor
Struct / Value / ListValue / AnyJSONField-backed fallback
wrapper typesoptional scalar handlers

Notes

  • AutoForm protobuf support is Buf descriptor-based
  • top-level JSON mode is editable
  • summary panels are opt-in
  • stepper flows are opt-in
  • explicit React config wins over schema metadata

Recent changes

  • patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested b…#133
  • minorv1.2.0Port the auto-form subsystem from ADP UI back to the registry.#122
  • minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
  • minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
  • majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
See full history →
Built by malinskibeniamin. The source code is available on GitHub.

On this page