Command Palette

Search for a command to run...

Customization

Treege allows you to customize the rendered form components to match your design system.

Custom Components

Override default input components with your own implementations.

Component Props

All custom components receive these props:

export type InputRenderProps<T extends InputType = InputType> = {
  /**
   * The node data for this input field
   */
  node: Node<InputNodeData>;
  /**
   * Current value of the input field (typed based on input type when T is specified)
   */
  value: InputValueTypeMap[T];
  /**
   * Unique field ID (nodeId)
   */
  id: string;
  /**
   * Field name (resolved using priority: name > label > nodeId)
   * Use this for the name and id attributes of the input element
   */
  name: string;
  /**
   * Function to update the input value
   * @param value - The new value (typed based on input type when T is specified)
   */
  setValue: (value: InputValueTypeMap[T]) => void;
  /**
   * Validation error message for this field (if any)
   */
  error?: string;
  /**
   * Translated label (already processed with current language)
   */
  label?: string;
  /**
   * Translated placeholder (already processed with current language)
   */
  placeholder?: string;
  /**
   * Translated helper text (already processed with current language)
   */
  helperText?: string;
  /**
   * Missing required fields on form submit (for submit inputs)
   */
  missingRequiredFields?: string[];
  /**
   * Whether the form is currently being submitted (for submit inputs)
   */
  isSubmitting?: boolean;
};

Example: Custom Text Input

import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import type { InputRenderProps } from "treege/renderer"
 
const CustomTextInput = ({
  node,
  value,
  setValue,
  error,
  label,
  placeholder,
  helperText,
  id,
  name
}: InputRenderProps<"text">) => {
  return (
    <div className="mb-4">
      <label className="block text-sm font-medium mb-1" htmlFor={id}>
        {label}
        {node.data.required && <span className="text-red-500 ml-1">*</span>}
      </label>
      <input
        id={id}
        name={name}
        type="text"
        value={value}
        onChange={(e) => setValue(e.target.value)}
        placeholder={placeholder}
        className="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500"
      />
      {error && <p className="text-red-500 text-sm mt-1">{error}</p>}
      {helperText && !error && (
        <p className="text-gray-500 text-sm mt-1">
          {helperText}
        </p>
      )}
    </div>
  )
}

Using Custom Components

import { TreegeRenderer } from "treege/renderer"
import { CustomTextInput } from "./CustomTextInput"
import { CustomNumberInput } from "./CustomNumberInput"
 
<TreegeRenderer
  flows={flows as Flow}
  onSubmit={handleSubmit}
  components={{
    inputs: {
      text: CustomTextInput,
      number: CustomNumberInput,
    },
  }}
/>

Important: Using id and name Props

The id and name props are automatically provided to your custom components. Always use them for proper form behavior:

const CustomInput = ({ id, name, value, setValue }: InputRenderProps<"text">) => (
  <input
    id={id}           // Use for accessibility (links label to input)
    name={name}       // Use for form submission and field identification
    value={value}
    onChange={(e) => setValue(e.target.value)}
  />
)

The name field is resolved with priority: node.data.name > node.data.label > node.id

Custom Submit Button

You can customize the submit button and its wrapper:

<TreegeRenderer
  flows={flow}
  onSubmit={handleSubmit}
  components={{
    submitButton: ({ label }) => (
      <button type="submit" className="btn-primary">
        {label || "Submit"}
      </button>
    ),
    submitButtonWrapper: ({ children, missingFields }) => (
      <div className="submit-wrapper">
        {children}
        {missingFields && missingFields.length > 0 && (
          <div className="tooltip">
            Please fill required fields: {missingFields.join(", ")}
          </div>
        )}
      </div>
    ),
  }}
/>

Submit Input with State

For submit type inputs, you get additional props:

const CustomSubmitInput = ({
  label,
  missingRequiredFields,
  isSubmitting
}: InputRenderProps<"submit">) => (
  <button
    type="submit"
    disabled={isSubmitting || (missingRequiredFields?.length ?? 0) > 0}
  >
    {isSubmitting ? "Submitting..." : label || "Submit"}
  </button>
)

Custom Validation

Add custom validation logic for specific fields.

Example: Email Validation

<TreegeRenderer
  flows={flow}
  onSubmit={handleSubmit}
  validators={{
    email: (value) => {
      if (!value) return "Email is required"
      if (!value.endsWith("@company.com")) {
        return "Must use company email address"
      }
    }
  }}
/>

Example: Password Strength

validators={{
  password: (value) => {
    if (!value) return "Password is required"
    if (value.length < 8) return "Must be 8+ characters"
    if (!/[A-Z]/.test(value)) return "Need uppercase letter"
    if (!/[0-9]/.test(value)) return "Need number"
  }
}}