Special Field

HTML Content

Render custom HTML or React components within a form for static content.

View as Markdown
stable
htmlcontentstaticcustom

Interactive demo of HTML content field

Note: The HTML field allows you to insert custom content into your forms.
/**
 * HTML Field Demo - Interactive examples of HTML content fields
 */

import { Form, FormBuilder } from '@saastro/forms';

import { FormProvider } from '@/components/FormProvider';
import { TooltipProvider } from '@/components/ui/tooltip';

const config = FormBuilder.create('html-demo')
  .layout('manual')
  .columns(12)
  .addField('infoBox', (f) =>
    f
      .type('html')
      .hideLabel()
      .value(
        '<div class="rounded-md border p-4 text-sm bg-blue-50 dark:bg-blue-950 border-blue-200 dark:border-blue-800"><strong class="text-blue-800 dark:text-blue-200">Note:</strong> <span class="text-blue-700 dark:text-blue-300">The HTML field allows you to insert custom content into your forms.</span></div>',
      )
      .optional()
      .columns({ default: 12 }),
  )
  .addField('name', (f) =>
    f
      .type('text')
      .label('Your Name')
      .placeholder('Enter your name')
      .required()
      .columns({ default: 12 }),
  )
  .addStep('main', ['infoBox', 'name'])
  .build();

export default function HtmlDemo() {
  const handleSubmit = (data: Record<string, unknown>) => {
    console.log('Form submitted:', data);
    alert('Form submitted! Check console for data.');
  };

  return (
    <TooltipProvider>
      <FormProvider>
        <Form config={config} onSubmit={handleSubmit} className="space-y-4" />
      </FormProvider>
    </TooltipProvider>
  );
}

Overview

The HTML field allows you to insert custom static content into your form. Use it for informational text, dividers, images, or any non-input content that needs to appear within the form flow.

The markup is sanitized by default (since 0.21) — see Sanitization. Forms authored in the Hub cannot run scripts on the site that embeds them; code-authored forms can opt out per field with .trustedHtml().

Usage

Basic HTML Content

import { FormBuilder } from '@saastro/forms';

const config = FormBuilder.create('form')
  .addField('notice', (f) =>
    f
      .type('html')
      .hideLabel()
      .value('<div class="p-4 bg-blue-50 rounded-lg"><p>Important information here</p></div>')
      .optional(),
  )
  .addStep('main', ['notice'])
  .build();

Section Divider

.addField('divider', (f) =>
  f.type('html')
    .hideLabel()
    .value('<hr class="my-4 border-t border-border" />')
    .optional()
)

Informational Message

.addField('legal', (f) =>
  f.type('html')
    .hideLabel()
    .value('<div class="rounded-md border p-4 text-sm text-muted-foreground bg-muted/50"><strong>Legal Notice:</strong> By continuing, you agree to our terms.</div>')
    .optional()
)

JSON Configuration

{
  "type": "html",
  "hideLabel": true,
  "content": "<div class=\"p-4 bg-blue-50 rounded\"><p>Important information here</p></div>"
}

Props

Prop Type Default Description
type 'html' - Field type (required)
label string - Label (often hidden)
hideLabel boolean false Hide the label
content string | ReactNode - HTML content to render
columns Partial<Record<Breakpoint, number>> - Grid columns by breakpoint
hidden boolean | function | ConditionGroup | Responsive false Hide the field
html string - The markup to render. Interpolates {{field}} tokens with current values, and is sanitized before insertion.
trustedHtml TrustedHtmlMarker - Opt out of the sanitizer for this block. Only settable from code via .trustedHtml() — the value is the TRUSTED_HTML marker, which JSON cannot express.

Sanitization

Every html block goes through sanitizeHtml before it reaches the DOM — the same allowlist sanitizer used for labels and success/error messages (policy in Form component → Success and error UI). Interpolated {{tokens}} are escaped first, then the whole result is sanitized, so a visitor cannot turn <a href="{{url}}"> into a javascript: link either.

The sanitizer is pure (no document), so a server-rendered form (<HubForm initialSchema>) emits exactly the same markup as the client — no hydration mismatch, no escaped text leaking into the page.

Trusted markup from code. A form written in TSX may need an <iframe> (a map, a booking widget) or inline style. Mark that block as trusted:

.addField('map', (f) =>
  f.type('html')
    .hideLabel()
    .value('<iframe src="https://www.google.com/maps/embed?…" title="Map"></iframe>')
    .trustedHtml()
    .optional(),
)

.trustedHtml() sets trustedHtml to the TRUSTED_HTML marker — a frozen object compared by identity (isTrustedHtml). It does not survive JSON.stringify/structuredClone, which is the point: a "trustedHtml": true in a Hub or builder JSON schema is ignored (with a console warning) and the block is sanitized like any other. Only code you wrote and reviewed can opt out.

Use Cases

  1. Section headers: Break forms into visual sections
  2. Help text: Detailed instructions mid-form
  3. Legal notices: Terms snippets
  4. Dividers: Visual separation
  5. Images: Inline graphics or diagrams

Conditional Display

.addField('premiumInfo', (f) =>
  f.type('html')
    .hideLabel()
    // Show only for premium users
    .hidden((values) => values.plan !== 'premium')
)

No Validation

The HTML field is purely presentational and doesn’t contribute to form data or validation. It’s skipped during form submission.

Styling

Custom Classes

.addField('notice', (f) =>
  f.type('html')
    .hideLabel()
    .optional()
    .classNames({
      wrapper: 'bg-blue-50 p-4 rounded-lg border border-blue-200',
    })
)

Responsive Layout

.addField('sidebar', (f) =>
  f.type('html')
    .hideLabel()
    .columns({ default: 12, md: 4 })
)

JSON Styling

{
  "type": "html",
  "hideLabel": true,
  "wrapper_className": "bg-blue-50 p-4 rounded-lg border border-blue-200",
  "columns": { "default": 12, "md": 6 },
  "content": "<div class=\"text-center\"><p class=\"font-semibold\">Important Notice</p></div>"
}

Note: HTML fields primarily use the wrapper class since there’s no input element. The content itself should have its own inline styles or classes.

All other fields are interactive. HTML is unique as a non-input field type.

Accessibility

  • Ensure HTML content is accessible
  • Use semantic HTML elements
  • Include proper headings hierarchy
  • Add alt text for images