Mixins / Inside-out composition

@foldkit/ui

@foldkit/ui components already expose a useful consumer seam when they hand typed attribute bundles to toView. foldkit-mixins-ui turns those bundles into named slots so the same Style and Behavior model can apply.

Button, Input, Checkbox, Disclosure, Dialog, Popover, Tooltip, Slider, Tabs, RadioGroup, Calendar and more are adapted.

There are two reasons a component may not be covered:

  1. Menu, Listbox, ComboBox and DatePicker build their full element tree internally and expose no toView/attribute bundle seam. There is nowhere for a mixin to attach without a component API change.
  2. Toast, FileDrop, VirtualList, DragAndDrop, Anchor, HoverIntent and Animation simply do not have adapters yet. FileDrop, for example, exposes the seam an adapter would need.

That distinction matters: one is an architectural boundary, the other is unfinished coverage.

Accessibility contracts

A11y.pattern describes structural accessibility expectations in the same vocabulary as Slots:

ts
const DialogPattern = A11y.pattern({
  trigger: { capability: Capability.Interactive, events: [Event.Click] },
  content: { capability: Capability.Container },
})

A11y.validate(DialogPattern, DialogSlots)

Validation is pure and DOM-independent. It checks the declared contract for missing or hidden slots, incompatible capabilities, and unpublished events or attributes. It does not inspect the rendered DOM and does not claim WCAG certification.

Style as data

Rule-based Style compiles deterministic rule text to deterministic class names. Equal rules share a class; server and client derive the same output without a render-time collector.

That gives the application explicit CSS data:

Style.pseudo / media / supports / container / nest
                    ↓
          deterministic class
          + deterministic CSS

Style.stylesheet(...) combines and deduplicates those rules. Input-dependent rule generation is intentionally restricted: Style.whenInput may choose static pieces, but it cannot create a new rule graph for every render.

Introspection and tooling

Because Slots and Mixins are data, the package can describe them without rendering a DOM tree. That is useful for tests, DevTools, generated documentation and agent tooling.

Slots.describe, Slot.describe and the Surface bridge expose serializable metadata, while resolver failures use stable diagnostic codes and structured details.

See the trace

Use the examples in increasing order of scope:

  • examples/mixins — the focused trace: Surface → SlotView → Style → Behavior.
  • examples/todo-app — a whole application using recipes, input-driven style, Theme, Behaviors and @foldkit/ui adapters.
  • examples/remote — the same view boundary over server-derived state.

pnpm demo runs all three.

Limits

  • Mixins can only attach where a view deliberately publishes a slot. That is the point: the extension surface is explicit rather than universal.
  • Behavior does not own state. Reach for Model/Submodel when state is required.
  • Rule-based Style produces CSS data; the application is responsible for including the resulting stylesheet.
  • A component that exposes no render/attribute seam cannot be adapted externally.
  • A11y.validate checks contracts, not rendered DOM or WCAG compliance.

Package map