S

Styling

Customize form field appearance with CSS classes, sizes, visibility, and dark mode.

Styling

@saastro/forms provides multiple ways to customize the appearance of your form fields. You can apply CSS classes to different parts of each field, control sizes, visibility, and dark mode. For grid layout configuration (columns, gap, breakpoints), see the Layout System page.

classNames() Method

The classNames() method allows you to add CSS classes to specific parts of a field:

.addField('email', (f) =>
  f.type('email')
    .label('Email')
    .required()
    .classNames({
      wrapper: 'bg-gray-50 p-4 rounded-lg',
      input: 'border-2 border-blue-500 focus:border-blue-700',
      label: 'text-lg font-bold text-blue-900',
      error: 'text-red-600 font-medium',
      helper: 'text-gray-500 italic',
    })
)

Available Class Targets

TargetDescriptionApplied To
wrapperContainer element<div> wrapping the entire field
inputInput control<input>, <textarea>, <select>, etc.
labelField label<label> element
errorError messageError text below the field
helperHelper textHelp text below the field

JSON Configuration

{
  "type": "email",
  "label": "Email",
  "wrapper_className": "bg-gray-50 p-4 rounded-lg",
  "input_className": "border-2 border-blue-500",
  "label_className": "text-lg font-bold",
  "error_className": "text-red-600 font-medium",
  "helper_className": "text-gray-500 italic",
  "schema": { "required": true, "format": "email" }
}

Field Order

Control the display order of fields (useful for responsive reordering):

.addField('sidebar', (f) =>
  f.type('text')
    .label('Sidebar')
    .required()
    .order({ default: 2, lg: 1 })  // Second on mobile, first on desktop
)

Field Size

Set the size of input fields:

.addField('search', (f) =>
  f.type('text')
    .label('Search')
    .required()
    .size('sm')  // 'sm' | 'md' | 'lg'
)
SizeDescription
smSmall - compact inputs
mdMedium - default size
lgLarge - prominent inputs

Options Layout (Groups)

For fields with multiple options (checkbox-group, radio, switch-group), control the options grid:

{
  "type": "checkbox-group",
  "label": "Interests",
  "optionsClassName": "grid grid-cols-2 md:grid-cols-3 gap-4",
  "options": [...]
}

Hiding Labels

Hide the label while keeping it accessible for screen readers:

.addField('search', (f) =>
  f.type('text')
    .label('Search')  // Still needed for accessibility
    .hideLabel()      // Visually hidden
    .placeholder('Search...')
    .required()
)

Conditional Visibility

Hide fields responsively by breakpoint:

.addField('mobileOnly', (f) =>
  f.type('text')
    .label('Mobile Field')
    .required()
    .hidden({ default: 'visible', lg: 'hidden' })  // Hidden on desktop
)

.addField('desktopOnly', (f) =>
  f.type('text')
    .label('Desktop Field')
    .required()
    .hidden({ default: 'hidden', lg: 'visible' })  // Hidden on mobile
)

Complete Example

const config = FormBuilder.create('styled-form')
  .columns(12)
  .gap(6)
  .addField('name', (f) =>
    f
      .type('text')
      .label('Full Name')
      .placeholder('John Doe')
      .required()
      .size('lg')
      .columns({ default: 12, md: 6 })
      .classNames({
        wrapper: 'bg-white shadow-sm rounded-lg p-4',
        input: 'text-lg',
        label: 'text-primary font-semibold',
      }),
  )
  .addField('email', (f) =>
    f
      .type('email')
      .label('Email Address')
      .placeholder('john@example.com')
      .required()
      .email()
      .columns({ default: 12, md: 6 })
      .classNames({
        wrapper: 'bg-white shadow-sm rounded-lg p-4',
        error: 'text-destructive font-medium',
      }),
  )
  .addField('bio', (f) =>
    f
      .type('textarea')
      .label('Bio')
      .placeholder('Tell us about yourself...')
      .rows(4)
      .optional()
      .columns({ default: 12 })
      .classNames({
        wrapper: 'bg-muted/50 rounded-lg p-4',
        input: 'min-h-[120px]',
        helper: 'text-xs text-muted-foreground',
      })
      .helperText('Optional - maximum 500 characters'),
  )
  .addStep('main', ['name', 'email', 'bio'])
  .build();

CSS Variables

The form components use CSS variables that you can override:

:root {
  --input-height: 2.5rem;
  --input-padding: 0.75rem;
  --label-font-size: 0.875rem;
  --error-font-size: 0.75rem;
}

Tailwind CSS Integration

All class names work with Tailwind CSS out of the box. Use any Tailwind utility classes:

.classNames({
  wrapper: 'space-y-2 transition-all duration-200',
  input: 'focus:ring-2 focus:ring-primary/50 hover:border-primary',
  label: 'tracking-wide uppercase text-xs',
})

Dark Mode

Classes work with Tailwind’s dark mode:

.classNames({
  wrapper: 'bg-white dark:bg-gray-800',
  input: 'border-gray-300 dark:border-gray-600',
  label: 'text-gray-900 dark:text-gray-100',
})