Skip to content
WingoUI
ComponentsTemplatesDocsPricingAI agentsBlogChangelogContact
Components
Buttons & Actions
ButtonButton GroupCopy ButtonDelete ButtonTheme ToggleToggleToggle Group
Inputs
Avatar UploadCalendarCheckboxColor PickerComboboxCountry SelectCron PickerCurrency InputDate PickerDate Range PickerDate Time PickerDuration PickerEmail InputEmoji PickerFile UploadFilter BuilderInputInput GroupListboxMasked InputMention InputMonth PickerMulti SelectNumber InputOTP InputPassword InputPhone InputRadio GroupRatingSearch InputSegmented ControlSelectSignature PadSliderSwitchTag InputTextareaTime PickerTimezone SelectTree Select
Forms
Address InputBooking WidgetCSV ImportFieldForm WizardInline EditInvoice line itemsRich Text EditorSchema Builder
Navigation
Back To TopBounce SidebarBreadcrumbCommand MenuGlobal SearchGooey NavHook SidebarNav MenuNav TabsPaginationProximity SidebarScroll ProgressSidebarStepsTable Of ContentsTabsTopbarUser MenuWorkspace Switcher
Overlays
Alert DialogContext MenuDialogDrawerDropdown MenuFamily DrawerHover CardPopoverShare DialogSheetShortcuts DialogTooltipTour
Feedback
AlertBannerCookie ConsentEmoji ReactionEmpty StateNotification BellNotification CenterProgressRoute ProgressSetup ChecklistSkeletonSpinnerToast
Data Display
Approval FlowAvatarBadgeCalendar ViewCode BlockComment ThreadContact cardCountdownDescription ListDiff ViewerFile ListFile managerFolderGanttJSON ViewerKbdLog ViewerMarkdownPresenceSchedulerTagTimelineTree View
Tables & Lists
Audit LogData TableDeal PipelineEditable GridKanbanListMembers tablePermissions MatrixTableTask ListVirtual List
Charts & Stats
Animated CounterArea chartBar chartDonut chartFunnel chartGaugeGitHub ActivityHeatmap calendarLeaderboardLine chartRadar chartRadial barSparklineStat CardTrackerTrend badge
Layout
AccordionApp ShellCardCollapsibleDetail LayoutEntity HeaderPage HeaderScroll AreaSeparatorSettings LayoutSplit ViewToolbar
Media
Audio PlayerCarouselCSV ViewerDOCX EditorDOCX ViewerExcel ViewerFile SystemImage compareImage CropperImage GalleryImage LightboxLocation MapPDF ViewerPowerPoint ViewerQR CodeStep PlayerVideo Player
AI Kit
Agent Status CardChat MessageChat ThreadChat WidgetConversation ListFluid OrbGrid RevealMatrix OrbModel PickerPrompt InputReasoning IndicatorRule BuilderSuggestion ChipsTool Call TimelineUsage MeterVoice InputWorkflow Canvas
Text & Effects
Background PatternGravity LettersMarqueeTerminalText AnimateText Rotate
Mobile
Action SheetBottom Tab BarFABHaptic ButtonHero HeaderInstall PromptLarge Title HeaderNotification BannerOnboarding CarouselPicker WheelPull To RefreshSlide To ConfirmSticky CTA BarSwipe DeckSwipeable Tabs
Commerce
Cart drawerCategory pickerCheckout stepsCoupon inputFavorite buttonFilter panelListing cardListing gridOrder trackerPricePrice rangePricing tableProduct galleryQuantity stepperReviewsSeller card
Marketing Sections
App Download SectionBlog Cards SectionCTA SectionFAQ SectionFeature GridFeature ShowcaseHero SectionIntegrations SectionLogo CloudMarketing FooterMarketing HeaderMarketing SectionNewsletter SectionPricing SectionStats SectionSteps SectionTeam SectionTestimonials SectionTimeline SectionVideo Section
Blocks
Admin Dashboard PageAdmin ShellAI Chat PageAPI Keys PageAutomation Builder PageCheckout PageCRM Contact PageCRM Pipeline PageError PagesInbox PageInvoice PageKPI DashboardListing Detail PageLogin PageMarketplace Home PageMarketplace Search PageMarketplace ShellMobile App HomeOnboarding FlowOrder History PagePost Listing WizardPricing PageProfile PageRegister PageSettings PageUsers Admin Page
Templates
About PageAcademy PageAccessibility PageAffiliate PageAgency PageAI Product PageAppointment Booking PageBlog PageBlog Post PageBug Bounty PageCareers PageChangelog PageCommunity PageCompare PageContact PageCustomer Story PageCustomers PageDemo Booking PageDevelopers PageDocs Home PageDownload PageEnterprise PageEvents PageExperts PageFAQ PageFeature PageGlossary PageHelp Article PageHelp Center PageHome Services PageImpact PageIntegration Detail PageIntegrations PageJob Detail PageLanding PageLaunch PageLegal PageManifesto PagePortfolio PagePress PageProduct Tour PagePromo PageReal Estate PageReferral PageResearch Report PageRestaurant PageRoadmap PageROI Calculator PageSecurity PageSolutions PageStartups PageStatus PageStorefront PageSwitch PageTemplate Gallery PageTravel PageWaitlist PageYear in Review Page
Hooks & Utilities
AI ChatChart kitCommerceDate utilsFile exportFile typesFiltersForm validationHapticsInvoicingMotion presetsNavigationNumber formatRelative timeRomanian locationsSpreadsheetText matchTonesUIProvideruseClipboarduseConsentuseControllableStateuseCooldownuseDebounceuseKeyboardInsetuseLocaleuseLongPressuseMediaControlsuseMediaQueryuseOverflowItemsusePersistedStateuseScrollEdgesuseScrollStateuseSelectionuseShortcutuseStickToBottom
GalleryTheme

AI Kit

Rule Builder

AI Kit

Rule Builder React component for Tailwind CSS v4

Reasoning IndicatorSuggestion Chips
Live props
Content
title
currency
locale
State
defaultEnabled
readOnly
disabled
Behavior
allowOr
maxActionsNo limit
validate
responsive
Layout
showSummary
summaryPosition
Appearance
size
tone
color
Code
import { RuleBuilder } from "@/components/ui/rule-builder"

<RuleBuilder />
  1. Home
  2. Components
  3. AI Kit
  4. Rule Builder
Pro

Rule Builder React component for Tailwind CSS v4

v1.0.0·Added in 1.0.0

A React rule builder for if-this-then-that automations: a trigger, conditions and ordered actions as sentence cards, read back as one sentence.

Pick a trigger from the dashed card, then press any step card to edit it: on a desktop the editor opens under the card, on a phone it rises as a bottom sheet. Edits are drafts: Apply, or Enter in a one-line field, saves them; Cancel, Escape or closing the sheet throws them away, and Apply stays dimmed until the required fields are filled (pressing it anyway points at them). Add conditions from the trigger's fields and choose whether all or any must match; add actions from the catalog and reorder them by dragging the grip (on a phone only the grip drags, the rest of the card scrolls the page), with Alt+Up or Alt+Down on a card, or with Space, the arrows and Space again on the grip. Each card's more button offers Edit, Duplicate, Move up, Move down and Remove (the trigger's offers Change trigger). Up, Down, Home and End move between the cards, Tab reaches a card's grip and more button. The header switch turns the rule on or off, and a stopped rule's cards dim; Test runs onTest and shows each action's outcome under the summary, which reads the whole rule as one sentence.

Rule Builder is a Pro React component in the React AI components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add rule-builder, together with Button, Currency Input, Drawer, Dropdown Menu, Field and 17 more and the npm packages @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities, motion and lucide-react.

Add to a project

Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup

npx wingo-ui@latest add rule-builder

Rule Builder is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans

Usage

Import RuleBuilder from @/components/ui/rule-builder.

import { useState } from "react"
import { FileWarning, Mail, Tag } from "lucide-react"
import {
  RuleBuilder,
  describeRule,
  type RuleActionDef,
  type RuleTriggerDef,
  type RuleValue,
} from "@/components/ui/rule-builder"

const days = (count: number) => (count === 1 ? "1 day" : `${count} days`)

const triggers: RuleTriggerDef[] = [
  {
    type: "invoice-overdue",
    label: "Invoice overdue",
    icon: <FileWarning />,
    params: [{ id: "days", label: "After", type: "days", required: true, defaultValue: 30 }],
    fields: [
      { id: "amount", label: "Amount", type: "currency" },
      {
        id: "city",
        label: "City",
        type: "select",
        options: [
          { value: "Austin", label: "Austin" },
          { value: "Portland", label: "Portland" },
        ],
      },
    ],
    summary: (params) => `an invoice is **${days(Number(params.days))}** overdue`,
  },
]

const actions: RuleActionDef[] = [
  {
    type: "send-email",
    label: "Send email",
    icon: <Mail />,
    tone: "info",
    params: [
      {
        id: "template",
        label: "Template",
        type: "select",
        required: true,
        options: [{ value: "final-notice", label: "Final notice" }],
      },
    ],
    summary: () => "send the **“Final notice”** email to the client",
  },
  {
    type: "add-tag",
    label: "Add tag",
    icon: <Tag />,
    params: [{ id: "tag", label: "Tag", type: "text", required: true }],
    summary: (params) => `add the **${params.tag}** tag`,
  },
]

export function OverdueRule({
  initial,
  onSave,
}: {
  initial: RuleValue
  onSave: (rule: RuleValue, sentence: string) => void
}) {
  const [rule, setRule] = useState(initial)
  return (
    <RuleBuilder
      title="Final notice after 30 days"
      triggers={triggers}
      actionTypes={actions}
      value={rule}
      onValueChange={(next) => {
        setRule(next)
        onSave(next, describeRule(next, { triggers, actions }))
      }}
      onTest={async () => ({ matched: true, message: "Invoice #2026-0371 would get the final notice." })}
      labels={{ test: "Dry run", enabled: "On" }}
    />
  )
}

Props

  • triggers*
    RuleTriggerDef[]

    The trigger catalog: { type, label, description?, icon?, group?, params?, fields?, summary? }. fields are the lib/filters fields the conditions can test (an invoice, a deal, a lead); summary(params) says the trigger in a sentence with values wrapped in **, like "an invoice is **30 days** overdue".

  • actionTypes*
    RuleActionDef[]

    The action catalog: { type, label, description?, icon?, group?, tone?, params?, summary?, multiple? }. tone colors the icon tile; multiple: false allows the action once per rule, so it leaves the picker once used. Named actionTypes because actions is the value's list.

  • titledefault ""
    ReactNode

    The rule's name in the header. It also names the switch, so a screen reader hears which rule turns on or off.

  • value
    RuleValue

    The rule: { trigger: { type, params } | null, conditions: FilterGroup, actions: { id, type, params }[] }. conditions is the lib/filters group a saved view or filter-builder uses, root level only.

  • defaultValue
    RuleValue

    The starting rule while uncontrolled; an empty rule (createRule()) when left out.

  • onValueChange
    (value: RuleValue) => void

    Every applied change: an editor's Apply, a picked trigger, an added, duplicated, moved or removed step, the all / any switch. Drafts in an open editor never reach it.

  • enabled
    boolean

    Controlled state of the header switch; pair it with onEnabledChange.

  • defaultEnableddefault true
    boolean

    Whether the rule starts switched on. A switched off rule's cards dim to 60%.

  • onEnabledChange
    (enabled: boolean) => void

    Fires when the header switch flips.

  • readOnlydefault false
    boolean

    Summaries only: cards without editors, menus, grips or add buttons, and a switch that shows its state. Test still works.

  • disableddefault false
    boolean

    Dims and locks everything: cards, menus, grips, add buttons, the switch and Test.

  • onTest
    (value: RuleValue) => Promise<RuleTestResult>

    Shows the Test button. While the promise runs the button spins; the result ({ matched, message?, steps?: { actionId, ok, message? }[] }) appears under the summary with each action's outcome, and any edit clears it. A rejection shows labels.testFailed.

  • allowOrdefault true
    boolean

    Lets the conditions match any instead of all: an all / any switch appears once there are two conditions. Off, conditions always join with and.

  • maxActionsdefault 0
    number

    The most actions a rule may have; the add button hides at the limit. A value below the current count removes nothing. 0 means no limit.

  • validatedefault "touched"
    "always" | "touched" | "never"

    When issues show on the cards (a red ring and a line saying what is missing): always, only after a step was edited once, or never (the app validates with validateRule).

  • responsivedefault true
    boolean

    Below 768px the step editors, the trigger, field and action pickers and the card menus open as bottom sheets. Off, phones get the desktop editor under the card.

  • showSummarydefault true
    boolean

    The dashed box that reads the whole rule as one sentence, values in bold.

  • summaryPositiondefault "bottom"
    "top" | "bottom"

    Where the sentence sits: under the header or after the actions.

  • sizedefault "md"
    "sm" | "md"

    Step cards 48 / 56px tall with 28 / 32px icon tiles, and sm / md fields in the desktop editors. On touch screens every card is at least 56px.

  • tonedefault "neutral"
    "neutral" | "brand" | "success" | "warning" | "danger" | "info"

    Colors the section pills (When, If, Then), the rails between them, the action numbers and the switch. neutral uses foreground tints.

  • colordefault ""
    string

    Any CSS color; replaces the tone for the pills, rails, numbers and switch. Leave empty to use the tone.

  • currencydefault "USD"
    string

    ISO 4217 code for currency params and currency fields that do not set their own; amounts read like $5,000.

  • localedefault ""
    string

    Formats numbers, money and dates in cards, fields and the sentence (en-US: $5,000). Empty uses the UIProvider's locale, then the browser's.

  • labels
    RuleBuilderLabelsInput

    Every visible and spoken string, merged over DEFAULT_RULE_BUILDER_LABELS: when, if, then, chooseTrigger, chooseAction, chooseField, addCondition, addAction, all, any, matchAll, matchAny, match, noConditions, noActions, edit, duplicate, moveUp, moveDown, remove, change, more, apply, cancel, required, back, enabled, test, testing, matched, notMatched, testFailed, stepOk, stepFailed, dismiss, summary, summaryEmpty, trigger, condition, field, operator, value, from, to, choose, search, noResults, daysUnit, days(count, formatted), unknownTrigger, unknownAction, unknownField, fieldUnavailable, incomplete, missing(label), sentence { when, if, then, and, or, comma, nothing }, operators (per filter operator), dragHandle(label), dragInstructions and announce { added, removed, moved, pickedUp, cancelled, pruned }.

  • classNames
    Partial<Record<"header" | "title" | "enabled" | "test" | "section" | "sectionLabel" | "rail" | "steps" | "step" | "stepButton" | "stepIcon" | "stepNumber" | "stepSummary" | "stepIssue" | "stepMenu" | "stepHandle" | "joiner" | "editor" | "editorFooter" | "combinator" | "add" | "empty" | "summary" | "testResult" | "sheet", string>>

    Extra classes for the inner parts; step is the card, stepButton the stretched button that opens its editor, sheet the phone editor.

  • className
    string

    Extra classes for the root, merged last. Override the tone vars here, e.g. [--tone-soft:...].

Parts

  • describeRule

    function

    The rule as one plain sentence, the same one the summary box shows: "When an invoice is 30 days overdue, if Amount ≥ $5,000, then …". Only complete conditions are read. Use it for notifications, logs and the automation list. Signature: (value: RuleValue, options: { triggers; actions; locale?; currency?; labels? }) => string.

  • validateRule

    function

    What keeps the rule from running, as { path: "trigger" | `conditions.${id}` | `actions.${id}`, message }: a missing or unknown trigger, incomplete conditions or conditions on fields the trigger lacks, unknown actions and required params left empty. Signature: (value: RuleValue, options: { triggers; actions; labels? }) => RuleIssue[].

  • createRule

    function

    An empty rule (no trigger, an and group with no conditions, no actions). Call it in handlers or state initializers. Signature: (overrides?: Partial<RuleValue>) => RuleValue.

  • DEFAULT_RULE_BUILDER_LABELS

    constant

    The English strings, to spread and override. Type: RuleBuilderLabels.

Examples

Empty rule

createRule(): pick a trigger from the dashed card; required params open its editor first.

Any of the conditions

allowOr with any selected: one of three conditions is enough, and the joiners say or.

Read only in a list

readOnly shows summaries only, for an Automations list; Edit brings the editors back.

Invalid steps

validate="always": an e-mail without a template and a condition without a value, ringed in red.

On a phone

The playground at 390px: every card opens its editor as a bottom sheet with Apply and Cancel.

Tone and color

tone="brand" or info colors the pills, rails, numbers and switch; a custom color replaces the tone.

Built-in labels, euros

No labels passed: the built-in strings, size sm, amounts in euros for en-US.

Dependencies

@dnd-kit/core@dnd-kit/sortable@dnd-kit/utilitiesmotionlucide-reactbuttoncurrency-inputdrawerdropdown-menufieldinputmulti-selectnumber-inputsegmented-controlselectswitchtextareause-controllable-stateuse-localeuse-media-queryai-chatcommercefiltersmotionnumber-formattonesui-config

Files

  • components/ui/rule-builder.tsx

FAQ

Is the React Rule Builder component free?

The preview, props and docs are free; copying the source needs a Pro plan. See plans.

How do I install Rule Builder?

Run npx wingo-ui@latest add rule-builder. It also adds the Wingo UI items it builds on (Button, Currency Input, Drawer, Dropdown Menu, Field and 17 more) and the npm packages @dnd-kit/core, @dnd-kit/sortable, @dnd-kit/utilities, motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/rule-builder.

How does Rule Builder behave on phones?

Below 768px the step editors, the trigger, field and action pickers and the card menus open as bottom sheets. Off, phones get the desktop editor under the card. The responsive prop controls this and is on by default.

See also

FilterslibFilter BuilderAutomation Builder PageWorkflow CanvasSelectSwitch

More React AI components

Suggestion ChipsTool Call TimelineUsage MeterFluid OrbGrid RevealMatrix Orb
All 17 React AI components

Articles using this component

  • React Workflow Builder: Triggers, Branches and AI Steps

    Build it: screens and apps · Oct 16, 2026 · 13 min read

PreviousReasoning IndicatorNext Suggestion Chips
Final notice after 30 days
When
If
and
Then
1
2
3

In short

When an invoice is 30 days overdue, if Amount ≥ $5,000 and City is any of Austin or Portland, then send the “Final notice” email to the client, personalized with AI, create a task for Julia Davis due in 2 days and add the Overdue tag.

Test record

Invoice #2026-0371

Wellness Pharmacy, Portland

$7,315.20 · 53 days overdue

Owner: Julia Davis

Test rule runs the rule on this record, with the current conditions.

Ready to save
validateRule
0 issues
validate=touchedresponsive=truemaxActions=0locale="" → en-UScurrency=USDenabled=true
JSON.stringify(value)
{
  "trigger": {
    "type": "invoice-overdue",
    "params": {
      "days": 30
    }
  },
  "conditions": {
    "id": "cond-root",
    "combinator": "and",
    "conditions": [
      {
        "id": "cond-amount",
        "field": "amount",
        "operator": "gte",
        "value": 5000
      },
      {
        "id": "cond-city",
        "field": "city",
        "operator": "any-of",
        "value": [
          "Austin",
          "Portland"
        ]
      }
    ]
  },
  "actions": [
    {
      "id": "act-email",
      "type": "send-email",
      "params": {
        "to": "client",
        "template": "somatie",
        "personalize": true
      }
    },
    {
      "id": "act-task",
      "type": "create-task",
      "params": {
        "assignee": "ioana",
        "due": 2,
        "title": "Call the client about the overdue invoice"
      }
    },
    {
      "id": "act-tag",
      "type": "add-tag",
      "params": {
        "tag": "restant"
      }
    }
  ]
}

Click a card to edit it in place. Drag the grip, or press Alt+Up / Alt+Down, to reorder actions.Tap a card: its editor opens as a sheet. Drag a grip to reorder.