Dify UI Rules
Use these rules whenever a review touches packages/dify-ui/ or code consuming @langgenius/dify-ui/*.
Before finalizing findings for those files, read the current local docs that apply:
packages/dify-ui/README.mdpackages/dify-ui/AGENTS.mdweb/docs/overlay.mdfor floating UIpackages/dify-ui/src/<primitive>/index.tsxfor the primitive being changed or consumed
Package Boundary
Flag in packages/dify-ui:
- Imports from
web/. - Dependencies on Next.js, i18n, ky, Jotai, Zustand, TanStack Query, oRPC, or business APIs.
- Business-specific component behavior that belongs in
web/. - Multiple unrelated primitives in one component folder.
packages/dify-ui is a primitive layer: Base UI headless components + cva + cn + Dify design tokens.
Imports And Exports
Flag:
- Consumer imports from
@langgenius/dify-uiwithout a subpath. - Missing
package.json#exportsentry for a new primitive. - Internal package imports using workspace subpaths instead of relative paths.
- Exported props using internal-only types that consumers cannot import from the component subpath.
Consumers use subpath exports such as @langgenius/dify-ui/button.
Props And State
Flag:
- Flattened props where related values need a discriminated union, such as
value/defaultValue,multiple/value, orclearable/onChange. - React state used only to mirror Base UI state for class names.
- JavaScript conditional class logic for visual states that the Dify UI/Base UI primitive already exposes through
data-*attributes or CSS variables. - Controlled props added when uncontrolled DOM state or CSS variables would be enough.
- Thin wrappers that rename Base UI parts without adding semantics.
Prefer Base UI/Dify UI data attributes and CSS variables for visual state: data-open, data-checked, data-disabled, data-highlighted, data-popup-open, group-data-*, peer-data-*, has-[:focus-visible], and primitive CSS variables such as anchor width or transform origin. Use JS conditional classes for product/business state that the primitive does not expose.
Forms
Flag:
- Form-like UI using unrelated
InputandButtonpieces without a submit boundary. - Text-like fields not composed through
FieldRoot,FieldLabel, andFieldControlwhen using Dify UI form semantics. - Select fields using
FieldLabelinstead ofSelectLabel. - Slider fields using a generic label instead of
SliderLabel. - Checkbox/radio groups missing
FieldsetRootandFieldsetLegend. - Field errors or descriptions rendered without
FieldDescription/FieldErrorrelationships.
Form is the submit boundary. Dify UI form primitives are not a form state-management framework; business validation and schema-driven behavior belong in web/.
Overlay Contract
Flag:
- Legacy web overlay imports in new or modified code.
- Manual portals around Dify UI overlay primitives.
- Call-site
z-*overrides on overlays. - Missing root
isolation: isolateassumptions when debugging overlay stacking. - Repeated backdrop, z-index, or portal chrome at call sites.
- Tooltip used for infotips, long text, or interactive content.
All Dify UI body-portalled overlays use z-50. Toast uses z-60. DOM order handles stacking between overlays.
Primitive Selection
Flag:
Tabsused for simple mode/filter/view selection whereSegmentedControlis the semantic primitive.SegmentedControlused wheretablist/tabpanelsemantics are required.Selectused for searchable or free-form input.Comboboxused for unrestricted search text where no selected option is remembered.Autocompleteused for closed-list selection.- Tooltip or PreviewCard used for content that must be reachable on touch or by screen readers.
Use:
Autocompletefor free-form text with optional suggestions.Comboboxfor searchable selected values from a collection.Selectfor closed, scannable option sets.Popoverfor infotips, help text, rich content, or interactions.
Bad Usage Patterns To Flag
Flag:
- Manually recreating UI behavior or chrome already owned by
@langgenius/dify-ui/*orweb/app/components/base/*, such as buttons, inputs, toggle groups, popovers, dropdown menus, alert dialogs, switches, avatars, scroll areas, toasts, borders, focus states, disabled states, segmented controls, or existing feature components. - Styling a raw Base UI primitive directly in
web/when a Dify UI primitive exists. - Wrapping a Dify UI primitive in a feature component that hides its label, error, disabled, or focus contract.
- Replacing a semantic primitive with a generic
divplus classes to match a screenshot. - Using
Tooltipbecause it is visually convenient when the content is actually help text or needs touch access. - Adding a
z-*override to make a child popup appear over a parent dialog. - Adding a new app-level wrapper around Dialog, Drawer, Popover, Select, or Combobox that repeats portal/backdrop/positioner logic.
- Using dify-ui
Inputas a drop-in replacement for legacy inputs that include search, clear, copy, unit, localized placeholder, or number normalization behavior. - Building a form row from loose text and controls instead of the matching Field/Form primitives.
- Adding component state only to style
data-open,data-checked,data-disabled, or highlighted states that Base UI already exposes. - Passing booleans down only so children can toggle classes already expressible with primitive
data-*selectors.
Tokens, Radius, And Styling
Flag:
radius-*class names.- Custom Tailwind
borderRadiusextension for Figma radius values. - Generic colors where semantic Dify tokens exist.
- Hardcoded design values where Dify tokens, component variants, or documented Figma radius mappings exist.
!important modifiers used to fight primitive styles instead of fixing the variant, selector, or component composition.- Manual class strings that duplicate primitive variants.
min-w-(--anchor-width)on picker popups when it defeats viewport clamping.
Use the Figma radius mapping from packages/dify-ui/AGENTS.md; for example --radius/sm maps to rounded-md, and --radius/md maps to rounded-lg.
Use ! only for a tightly scoped compatibility override after confirming the primitive API, data attributes, and selector structure cannot express the state.
Focus Details
Flag focus rings attached to the wrong element. For example, Base UI Slider.Thumb focuses an internal input[type=range], so the visible thumb wrapper needs has-[:focus-visible] rather than direct wrapper focus-visible.
Custom SVG Icons
Flag:
- New generated React icon components or JSON files under
web/app/components/base/icons/src/...for custom SVG icons. - Custom SVG icons consumed outside the Tailwind
i-custom-*icon class pipeline. - Generated
packages/iconify-collections/custom-*/icons.jsondiffs where unrelated existing icons lost or changed intrinsicwidthorheight.
New custom SVG icons belong in packages/iconify-collections/assets/.... Regenerate with pnpm --filter @dify/iconify-collections generate, validate with pnpm --filter @dify/iconify-collections check:dimensions, and consume the generated icon with Tailwind i-custom-* classes.