diff --git a/.agents/skills/frontend-code-review/SKILL.md b/.agents/skills/frontend-code-review/SKILL.md index b5e262affc7..e99d90c3590 100644 --- a/.agents/skills/frontend-code-review/SKILL.md +++ b/.agents/skills/frontend-code-review/SKILL.md @@ -27,7 +27,7 @@ Read only the packs matched by the diff: - Stable Dify runtime invariants in the named paths: [`references/dify-invariants.md`][dify-invariants] - General TypeScript or styling quality not owned above: [`references/code-quality.md`][code-quality] -Read `packages/dify-ui/README.md`, `packages/dify-ui/AGENTS.md`, `web/docs/overlay.md`, or `web/docs/test.md` only when the reviewed code falls under that contract. Check current official documentation when local code and bundled references do not settle a framework, browser, or accessibility behavior. +Read `packages/dify-ui/README.md`, `packages/dify-ui/AGENTS.md`, `packages/dify-ui/docs/overlays.md`, or `web/docs/test.md` only when the reviewed code falls under that contract. Check current official documentation when local code and bundled references do not settle a framework, browser, or accessibility behavior. ## Severity And Output diff --git a/.agents/skills/frontend-code-review/references/dify-ui.md b/.agents/skills/frontend-code-review/references/dify-ui.md index 7da481ef6e6..dec2bf209cb 100644 --- a/.agents/skills/frontend-code-review/references/dify-ui.md +++ b/.agents/skills/frontend-code-review/references/dify-ui.md @@ -1,142 +1,29 @@ -# Dify UI Rules +# Dify UI Review Routing -Use these rules whenever a review touches `packages/dify-ui/` or code consuming `@langgenius/dify-ui/*`. +Use this reference when a review touches `packages/dify-ui/` or consumes +`@langgenius/dify-ui/*`. It routes to owner documentation; it does not redefine package +contracts. -Before finalizing findings for those files, read the current local docs that apply: +Read `packages/dify-ui/AGENTS.md` and the primitive implementation first, then only the matching +owner: -- `packages/dify-ui/README.md` -- `packages/dify-ui/AGENTS.md` -- `web/docs/overlay.md` for floating UI -- `packages/dify-ui/src//index.tsx` for the primitive being changed or consumed +| Review area | Canonical owner | +| ------------------------------------------ | ------------------------------------------------------------------- | +| Package boundary and document routing | `packages/dify-ui/AGENTS.md` | +| Imports, exports, public types, and anatomy | `packages/dify-ui/docs/authoring.md` | +| Button or icon-only actions | `packages/dify-ui/src/button/README.md`, `packages/dify-ui/src/icon-button/README.md` | +| Compound inputs | `packages/dify-ui/src/input-group/README.md` | +| Forms and field semantics | `packages/dify-ui/docs/forms.md` | +| Selection and typed values | `packages/dify-ui/docs/selection.md` | +| Portals, layers, and floating surfaces | `packages/dify-ui/docs/overlays.md` | +| Tailwind and radius tokens | `packages/dify-ui/docs/styling.md` | +| Package tests and Storybook | `packages/dify-ui/docs/testing.md` | -## Package Boundary +For consumer code under `web/`, also read `web/AGENTS.md` for application-owned reuse policy and +`packages/dify-ui/README.md` for the available public subpaths. -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-ui` without a subpath. -- Missing `package.json#exports` entry 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. -- Canonical primitive boundaries or their associated public types using a redundant `Root` suffix when no higher-level convenience component exists in the same subpath. - -Consumers use subpath exports such as `@langgenius/dify-ui/button`. - -Canonical boundaries use the primitive name and matching public types (`Select` / `SelectProps`). Keep `Root` only to distinguish a low-level anatomy root from a higher-level convenience component (`CheckboxRoot` / `Checkbox`); implementation aliases should still show their Base UI source (`BaseSelect.Root.Props`). - -## Props And State - -Flag: - -- Flattened props where related values need a discriminated union, such as `value` / `defaultValue`, `multiple` / `value`, or `clearable` / `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. -- Generic Base UI selection primitives wrapped without preserving their value generics, such as `Select.Root`, `RadioGroup`, or `Radio.Root`. -- Shared select/radio option components that type selected values as `string` while callers pass enums, unions, booleans, numbers, objects, or nullable placeholder values. - -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. - -For non-string `Select` and `RadioGroup` values, prefer explicit domain generics at the root and at child value carriers. JSX children do not inherit the parent generic, so `RadioGroup` should compose with `Radio`, `RadioItem`, or option values from a typed collection. For `Select`, prefer the Base UI `items` collection pattern for typed value-to-label rendering, and flag string coercion helpers used only to recover display labels. - -## Forms - -Flag: - -- Form-like UI using unrelated `Input` and `Button` pieces without a submit boundary. -- Text-like fields not composed through `Field`, `FieldLabel`, and `Input` or `InputGroupInput` when using Dify UI form semantics. -- Prefixes, suffixes, or actions manually layered over `Input` instead of using the canonical `InputGroup` composition. -- Select fields using `FieldLabel` instead of `SelectLabel`. -- Slider fields using a generic label instead of `SliderLabel`. -- Checkbox/radio groups missing `Fieldset` and `FieldsetLegend`. -- Field errors or descriptions rendered without `FieldDescription` / `FieldError` relationships. - -`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: isolate` assumptions 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: - -- `Tabs` used for simple mode/filter/view selection where `SegmentedControl` is the semantic primitive. -- `SegmentedControl` used where `tablist` / `tabpanel` semantics are required. -- `Select` used for searchable or free-form input. -- `Combobox` used for unrestricted search text where no selected option is remembered. -- `Autocomplete` used for closed-list selection. -- Tooltip or PreviewCard used for content that must be reachable on touch or by screen readers. - -Use: - -- `Autocomplete` for free-form text with optional suggestions. -- `Combobox` for searchable selected values from a collection. -- `Select` for closed, scannable option sets. -- `Popover` for 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/*` or `web/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 `div` plus classes to match a screenshot. -- Using `Tooltip` because 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 `Input` as 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 `borderRadius` extension 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/README.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.json` diffs where unrelated existing icons lost or changed intrinsic `width` or `height`. - -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. +Treat the implementation, public types, tests, and stories as evidence for the documented +contract. If they disagree, identify the actual owner before reporting a finding. Read current +official Base UI documentation and installed type declarations for upstream-derived behavior. +Report only a reproducible contract violation or observable defect, not a preference inferred from +this routing file. diff --git a/.agents/skills/frontend-testing/SKILL.md b/.agents/skills/frontend-testing/SKILL.md index 168a3906c16..a09aa940b1d 100644 --- a/.agents/skills/frontend-testing/SKILL.md +++ b/.agents/skills/frontend-testing/SKILL.md @@ -13,4 +13,4 @@ description: Use when writing or changing Vitest or React Testing Library tests 4. Run the focused spec before the affected suite and relevant static checks. 5. Report the behavior verified and any remaining browser, visual, or end-to-end risk. -Recommend deleting low-value tests as readily as adding missing behavior coverage. Use `web/docs/test.md` for policy and Web commands; use the `packages/dify-ui/README.md` Development section for Dify UI commands. +Recommend deleting low-value tests as readily as adding missing behavior coverage. Use `web/docs/test.md` for policy and Web commands; use `packages/dify-ui/docs/testing.md` for Dify UI commands. diff --git a/.agents/skills/how-to-write-component/SKILL.md b/.agents/skills/how-to-write-component/SKILL.md index f9c0a2ccc64..2b0e50f538d 100644 --- a/.agents/skills/how-to-write-component/SKILL.md +++ b/.agents/skills/how-to-write-component/SKILL.md @@ -32,7 +32,7 @@ Use this skill to route component architecture decisions to its bundled referenc 1. Identify the behavior owner and the public contract being changed. 2. Read the nearby implementation, tests, and only the routed skill references. 3. Implement one coherent vertical slice. Do not expand into equivalent patterns elsewhere unless the current contract cannot be completed without them. -4. Verify observable behavior at the narrowest sufficient boundary, then run the checks documented by the owning package: `web/docs/test.md` or `web/docs/lint.md` for Web, and the `packages/dify-ui/README.md` Development section for Dify UI. +4. Verify observable behavior at the narrowest sufficient boundary, then run the checks documented by the owning package: `web/docs/test.md` or `web/docs/lint.md` for Web, and `packages/dify-ui/docs/testing.md` for Dify UI. [data]: references/data.md [interactions]: references/interactions.md diff --git a/.agents/skills/how-to-write-component/references/interactions.md b/.agents/skills/how-to-write-component/references/interactions.md index a89f2b88633..68233987b61 100644 --- a/.agents/skills/how-to-write-component/references/interactions.md +++ b/.agents/skills/how-to-write-component/references/interactions.md @@ -1,6 +1,6 @@ # Component Interactions And Overlays -Read this document when a change involves application hotkeys, focus, dialogs, menus, popovers, or other secondary surfaces. Overlay primitive selection and layering are owned by the [overlay guide]. +Read this document when a change involves application hotkeys, focus, dialogs, menus, popovers, or other secondary surfaces. Overlay primitive selection and layering are owned by the [overlay contract]. ## Focus And Semantics @@ -20,12 +20,11 @@ Read this document when a change involves application hotkeys, focus, dialogs, m ## Secondary Surfaces -- Follow `web/docs/overlay.md` for primitive choice. Dify UI primitives are the default, with package-approved Web wrappers such as `Infotip` where the overlay guide allows them. +- Follow the [overlay contract] for primitive choice and shared mechanics. The nearest consumer `AGENTS.md` owns application-specific composite reuse policy. - Separate behavior ownership from placement ownership: the action may own trigger, open state, and menu content while the caller owns slots, offsets, and alignment. - Keep menu and dialog surfaces as siblings when a menu command opens a dialog. Mount the dialog outside popup content. - Mount controlled overlays unconditionally unless unmounting is required for performance or reset semantics. Prefer keyed or owner-local reset over conditional wrappers. - Put query and mutation work inside dialog or alert-dialog content when it should mount only after opening. - Prefer uncontrolled roots when the primitive can own open state. Use controlled state only for business coordination, analytics, cleanup, or explicit reset behavior. -- Do not add manual portals or call-site z-index escalation. Fix ownership and stacking structure at the shared boundary. -[overlay guide]: ../../../../web/docs/overlay.md +[overlay contract]: ../../../../packages/dify-ui/docs/overlays.md diff --git a/packages/dify-ui/AGENTS.md b/packages/dify-ui/AGENTS.md index 9b877e2a8dd..5d02acdc1e0 100644 --- a/packages/dify-ui/AGENTS.md +++ b/packages/dify-ui/AGENTS.md @@ -1,35 +1,41 @@ # @langgenius/dify-ui -This package owns shared design tokens, CSS-first Tailwind styles, the `cn()` utility, and headless primitives consumed by `web/`. Read only the matching topic in [`README.md`] for public imports, forms, typed values, pickers, overlays, tokens, or tests. +This file owns the package boundary and routes detailed contracts. Start from the [package index], +then read only the guide for the contract being changed. -## Component Authoring Rules +## Package boundary -- Build primitives from `@base-ui/react`, `cva`, and `cn`. -- Use relative cross-component imports inside the package and subpath exports such as `@langgenius/dify-ui/button` from consumers. Add a matching `package.json#exports` entry for each public primitive. -- Keep one primitive per `src//` folder with optional colocated stories and tests. -- Do not import from `web/` or depend on Next.js, i18n, application state, or data-fetching libraries. -- Preserve upstream Base UI anatomy and generic value contracts. Use discriminated unions when one prop changes the valid shape of related props; do not flatten those relationships or hard-code selectable values to `string`. -- Export shared public types from the owning component subpath. -- Prefer Base UI data attributes and CSS variables for visual states; do not mirror primitive state in React solely to add classes. -- When a Base UI API or selector contract is unclear, read the current official documentation and local `@base-ui/react` type declarations before coding. +- Keep this an independent primitive package. Do not import from application packages or depend on + routing, i18n, application state, schemas, data fetching, or business APIs. +- Prefer `@base-ui/react` when it owns the required headless behavior. Style primitives with `cva`, + `cn`, and Dify design tokens. Keep one primitive per `src//` folder with optional colocated + stories and tests. +- Prefer Base UI data attributes and CSS variables for visual states. Do not mirror primitive state + in React solely to add classes. +- When an upstream API or selector contract is unclear, read the current official Base UI + documentation and installed `@base-ui/react` declarations before coding. -Use the README sections as the detailed owners: +## Contract owners -- [Button and icon-button contracts] -- [Form and input composition] -- [Imports and public boundaries] -- [Typed value contracts] -- [Search and picker selection] -- [Tailwind and Figma radius mapping] -- [Overlay and portal contracts] -- [Development and test boundaries] +- Imports, exports, naming, public types, generics, and anatomy: [Public API authoring] +- Button and icon-only action behavior: [Button contract] and [Icon Button contract] +- Compound input behavior: [Input Group contract] +- Form structure and labels: [Forms] +- Picker choice and typed values: [Selection] +- Portals, layering, and floating-surface semantics: [Overlays] +- Tailwind integration and radius mapping: [Styling] +- Package test ownership and setup: [Testing and development] -[Button and icon-button contracts]: README.md#button-loading-and-disabled-contract -[Development and test boundaries]: README.md#development -[Form and input composition]: README.md#form-contract -[Imports and public boundaries]: README.md#imports -[Overlay and portal contracts]: README.md#overlay--portal-contract -[Search and picker selection]: README.md#search-and-picker-selection -[Tailwind and Figma radius mapping]: README.md#tailwind-css-v4-integration -[Typed value contracts]: README.md#typed-value-contracts -[`README.md`]: README.md +A component needs a local README only when it owns a substantial Dify-specific contract that its +types, stories, and upstream documentation do not express. Do not create one for completeness. + +[Button contract]: src/button/README.md +[Forms]: docs/forms.md +[Icon Button contract]: src/icon-button/README.md +[Input Group contract]: src/input-group/README.md +[Overlays]: docs/overlays.md +[Public API authoring]: docs/authoring.md +[Selection]: docs/selection.md +[Styling]: docs/styling.md +[Testing and development]: docs/testing.md +[package index]: README.md diff --git a/packages/dify-ui/README.md b/packages/dify-ui/README.md index 1f0987dfc40..0b1fb45c76c 100644 --- a/packages/dify-ui/README.md +++ b/packages/dify-ui/README.md @@ -1,17 +1,15 @@ # @langgenius/dify-ui -Shared UI primitives, design tokens, CSS-first Tailwind styles, and the `cn()` utility consumed by Dify's `web/` app. +Independent UI primitives, design tokens, CSS-first Tailwind styles, and the `cn()` utility for +Dify products. -The primitives are thin, opinionated wrappers around [Base UI] headless components, styled with `cva` + `cn` and Dify design tokens. -For upstream component docs, start from the [Base UI docs index]. +Most interactive primitives are thin, opinionated wrappers around [Base UI] headless components. +Dify-authored primitives use semantic HTML, `cva`, `cn`, and Dify design tokens. The package is +private to the workspace, but its public subpaths are treated as stable package boundaries. -> `private: true` — this package is consumed by `web/` via the pnpm workspace and is not published to npm. Treat the API as internal to Dify, but stable within the workspace. +## Usage -## Installation - -Already wired as a workspace dependency in `web/package.json`. Nothing to install. - -For a new workspace consumer, add: +Add the workspace dependency: ```jsonc { @@ -21,317 +19,78 @@ For a new workspace consumer, add: } ``` -## Imports +Import from a public subpath. The package intentionally has no root barrel: -Always import from a **subpath export** — there is no barrel: - -```ts +```tsx import { Button } from '@langgenius/dify-ui/button' -import { cn } from '@langgenius/dify-ui/cn' import { Dialog, DialogContent, DialogTrigger } from '@langgenius/dify-ui/dialog' -import { Drawer, DrawerPopup, DrawerTrigger } from '@langgenius/dify-ui/drawer' import { Field, FieldLabel } from '@langgenius/dify-ui/field' -import { Form } from '@langgenius/dify-ui/form' -import { IconButton } from '@langgenius/dify-ui/icon-button' -import { InputGroup, InputGroupAddon, InputGroupInput } from '@langgenius/dify-ui/input-group' import { Input } from '@langgenius/dify-ui/input' -import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd' -import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' -import { SegmentedControl, SegmentedControlItem } from '@langgenius/dify-ui/segmented-control' -import { Textarea } from '@langgenius/dify-ui/textarea' -import { Toggle } from '@langgenius/dify-ui/toggle' -import '@langgenius/dify-ui/styles.css' // once, in the app root +import { cn } from '@langgenius/dify-ui/cn' +import '@langgenius/dify-ui/styles.css' ``` -Importing from `@langgenius/dify-ui` (no subpath) is intentionally not supported — it keeps tree-shaking trivial and makes Storybook / test coverage attribution per-primitive. - -The canonical boundary exported from a primitive subpath uses the primitive name without a `Root` suffix, and its public types follow the same name (`Select` / `SelectProps`, `Drawer` / `DrawerProps`). Keep `Root` only when the subpath exposes both a low-level anatomy root and a higher-level convenience component, such as `CheckboxRoot` / `Checkbox` or `PaginationRoot` / `Pagination`. Implementation code should continue to reference the upstream Base UI anatomy explicitly through names such as `BaseSelect.Root.Props`. - -### Public type contracts - -Every runtime component exported from a primitive subpath must have an accurate, importable props type with the matching name (`DialogContent` / `DialogContentProps`). Define Dify-authored composite props at the Dify UI boundary; use direct aliases for unchanged Base UI parts instead of copying their shapes. - -Treat each `src//index.tsx` as an explicit public API boundary. Keep component, factory, hook, and type declarations module-local, then publish the complete surface through separate `export { ... }` and `export type { ... }` manifests at the bottom of the file. Do not mix scattered inline exports with the manifest or use a wildcard export; omission from the manifest keeps implementation helpers private. - -Preserve generic relationships end to end. Generic public components and their props must carry the same caller-owned type parameters, including picker `Value` / `Multiple`, form values, radio values, slider values, and overlay payloads and handles. Never erase those relationships with `any` or a hard-coded `string`; use `unknown` only as the safe default for an independently consumed anatomy part whose value cannot be inferred from its parent through JSX. - -Do not add a root-only generic when independently rendered JSX anatomy can produce values outside that root's inferred type. Preserve the upstream contract until the complete component family can enforce one value type; otherwise the generic gives callbacks a narrower type than the runtime can guarantee. `Tabs` intentionally follows Base UI's value contract for this reason. - -Keep the public type surface smaller than the upstream Base UI namespace. A type is not public merely because Base UI provides a name for it, because Dify gives an internal contract a descriptive alias, or because an earlier implementation happened to export it. In addition to matching component props, export a type only when it pairs with a public factory or has a concrete workspace consumer that cannot express the contract clearly through the matching props type. Remove legacy aliases that have no matching runtime API or real consumer; package-local tests and stories can derive narrow values from canonical props instead of preserving an otherwise unused export. Dify-authored options, states, and controlled/uncontrolled branches stay private when the matching props type already expresses them. - -State, event details and reasons, actions, and controlled/uncontrolled composition helpers are private by default. Public props already provide contextual typing for inline render and event callbacks. Export one of these narrower contracts only when Dify defines the state or event itself, a public factory requires the named type, or an external consumer needs to name it independently. Apply the same rule to Dify-authored components such as FileTree, Pagination, ProgressCircle, StatusDot, and Toast; being implemented locally does not justify a broader API. - -Keep implementation-only render helpers, context values, styling helpers, and upstream passthrough aliases private. When a wrapper consumes `className` through `cn()`, omit the upstream state-callback form and expose `className?: string`; public types must describe behavior the wrapper actually implements. +Import `styles.css` once from the consumer's root stylesheet or entrypoint. ## Primitives -| Category | Subpath | Notes | -| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | -| Actions | `./button`, `./icon-button`, `./toggle` | Visible-label actions, icon-only commands, and persistent toggles. | -| Controls | `./segmented-control` | SegmentedControl for mode, filter, and view selection. | -| Display | `./collapsible`, `./kbd` | Collapsible disclosure primitive; keyboard input and shortcut keycap primitives. | -| Feedback | `./meter`, `./progress`, `./status-dot`, `./toast` | Inline and asynchronous status primitives; Toast owns the `z-60` layer. | -| Form | `./form`, `./field`, `./fieldset`, `./input`, `./input-group`, `./textarea`, `./checkbox`, `./checkbox-group`, `./radio`, `./number-field`, `./select`, `./slider`, `./switch` | Native form boundary, field semantics, and controls. | -| Layout | `./scroll-area` | Custom-styled scrollbar over the host viewport. | -| Media | `./avatar` | Avatar root, image, and fallback primitives. | -| Navigation | `./file-tree`, `./pagination`, `./tabs` | FileTree for preview-oriented file disclosure lists; Pagination for page navigation; Tabs for panels. | -| Overlay / menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | Portalled. See [Overlay & portal contract] below. | -| Search / pickers | `./autocomplete`, `./combobox`, `./select` | Search input, searchable picker, and closed picker. | +| Category | Public subpaths | +| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Actions | `./button`, `./icon-button`, `./toggle` | +| Controls | `./segmented-control` | +| Display | `./collapsible`, `./kbd` | +| Feedback | `./meter`, `./progress`, `./status-dot`, `./toast` | +| Form | `./form`, `./field`, `./fieldset`, `./input`, `./input-group`, `./textarea`, `./checkbox`, `./checkbox-group`, `./radio`, `./number-field`, `./select`, `./slider`, `./switch` | +| Layout | `./scroll-area` | +| Media | `./avatar` | +| Navigation | `./file-tree`, `./pagination`, `./tabs` | +| Overlay and menu | `./alert-dialog`, `./context-menu`, `./dialog`, `./drawer`, `./dropdown-menu`, `./popover`, `./preview-card`, `./tooltip` | +| Search and pick | `./autocomplete`, `./combobox`, `./select` | Utilities: -- `./cn` — `clsx` + `tailwind-merge` wrapper. Use this for conditional class composition. -- `./styles.css` — the one CSS entry that ships the design tokens, theme variables, and project utilities/components. Import it once from the app root. +- `./cn` composes conditional classes with `clsx` and `tailwind-merge`. +- `./styles.css` provides design tokens, theme variables, and shared utilities. -## Button loading and disabled contract +## Guides -`Button` owns the spacing between direct children. Regular (`medium`) and Large sizes use -4px and 6px gaps. Small uses 3px for Primary and 4px for the other variants. Do not add icon -margins or a standard `gap-*` at call sites; use a Button `className` override only for a -documented layout exception. +Start here, then open only the guide for the contract being changed. Component-specific Dify +behavior lives beside the component. Contracts shared by several primitives live in `docs/`. +Upstream behavior remains owned by the [Base UI documentation]. -`Button` keeps normal `disabled` controls native-disabled by default so unavailable actions are removed from the keyboard focus order. +### Component guides -When `loading` is true, `Button` defaults `focusableWhenDisabled` to true. Loading represents an action that has already been triggered and is temporarily pending, so the button remains focusable while Base UI still suppresses click, pointer, keyboard activation, and submit-button activation. Pass `focusableWhenDisabled={false}` only when a loading button should use native disabled behavior. +| Guide | Dify-owned contract | +| ------------- | ------------------------------------------------------------------------------------------- | +| [Button] | Action semantics, submit and link choices, loading versus disabled, and content spacing. | +| [Icon Button] | Accessible names, decorative glyphs, appearance ownership, and primitive composition. | +| [Input Group] | Compound input anatomy, shared-surface ownership, DOM order, focus, and interactive addons. | -## Icon button contract +### Cross-component guides -Use `IconButton` for a command represented by one icon and no visible text. Use `Button` when the control has a visible label, including buttons with leading or trailing icons. +| Guide | Scope | +| ------------------------- | ------------------------------------------------------------------------ | +| [Forms] | Native submit boundaries, fields, labels, grouped controls, and errors. | +| [Selection] | Typed values and choosing among segmented controls, pickers, and radios. | +| [Overlays] | Portals, root isolation, layering, trigger composition, and semantics. | +| [Styling] | Tailwind CSS integration and the Figma radius mapping. | +| [Public API authoring] | Subpath exports, naming, public types, generics, and private helpers. | +| [Testing and development] | Package commands, test ownership, accessibility, and animation setup. | -Pass exactly one React element containing the decorative glyph and provide either `aria-label` or `aria-labelledby`. Mark the glyph or its decorative wrapper `aria-hidden="true"`; `IconButton` does not add that attribute to its child: +## Contributing -```tsx - - -``` +Read [component authoring rules] before modifying the package, then open only the matching owner +guide. This index intentionally does not duplicate those contracts. -Every icon button must have an `aria-label` or `aria-labelledby`; a tooltip is only a visual enhancement. The child chooses the glyph and its optical size. Omit `variant` for the IconButton-specific neutral appearance; the other appearance names align with `Button`. Use `tone="destructive"` for destructive intent. - -Size, radius, colors, hover, disabled, and focus-visible styles belong to `IconButton`. Use `className` for external layout or selectors driven by the composed primitive or business-state owner; do not recreate an existing appearance variant. - -When Toggle, Menu, Popover, Tooltip, or Collapsible owns the interaction state, keep that primitive outside and use its `render` prop to render `IconButton` as the final button. `IconButton` preserves Base UI Button's `render`, `nativeButton`, event, and ref composition. - -## Segmented control contract - -`SegmentedControl` is Dify's required single-choice primitive for mode, filter, and view selection. It is built on Base UI `RadioGroup` + `Radio`, so `value`, `defaultValue`, and `onValueChange` use the caller's scalar domain value. Provide either `value` or `defaultValue`; an active item cannot be toggled off. Use `Tabs` instead when the UI needs `tablist` / `tabpanel` semantics. - -Keyboard interaction follows the radio-group model: `Tab` enters on the selected item, and an arrow key moves focus and immediately selects the next enabled item. - -## Form contract - -Dify UI's form primitives are a Base UI composition layer for native form semantics, field accessibility, and design-system styling. They are intentionally not a form state-management framework. See the upstream [Base UI forms handbook], [Base UI Form], [Base UI Field], and [Base UI Fieldset] docs for the underlying component contracts. - -Use `Form` for the submit boundary. It renders a native `
`, preserves Enter-to-submit and submit-button behavior, and adds Base UI's `onFormSubmit`, `errors`, `actionsRef`, and `validationMode` APIs for structured values and consolidated field validation. Prefer it over a bare `` when the form is composed with Dify UI fields. - -Use `Field` when a text control needs Base UI field semantics such as shared name, label, validation, description, or error state. A standalone `Input` may instead use a native `