Mixins / Inside-out composition

A small example

The component author publishes slots:

ts
const FieldSlots = Slots.define({
  root: Slot.make({ capability: Capability.Container }),
  input: Slot.make({
    capability: Capability.TextInput,
    events: [Event.Input],
    attributes: [Attr.AriaInvalid],
  }),
})

A design system can style those points without editing the view:

ts
const FieldStyle = Style.forSlots(FieldSlots)({
  root: Style.class('field'),
  input: Style.class('field-input'),
})

A feature can independently attach behavior:

ts
const Validation = Behavior.forSlots(FieldSlots)<FieldInput, Message>({
  input: Behavior.slot({
    requires: { capability: Capability.TextInput, attributes: [Attr.AriaInvalid] },
    attributes: ({ input, h }) => [h.AriaInvalid(input.invalid)],
  }),
})

And the view resolves both through the same published points:

ts
const Field = SlotView.forMessages<Message>()
  .define(FieldSlots, (input: FieldInput, slots, h) =>
    h.label(slots.root.attrs(), [
      h.input(
        slots.input.attrs([
          h.Value(input.value),
          h.OnInput(value => Message.ChangedValue({ value })),
        ]),
      ),
    ]),
  )
  .pipe(Style.attach(FieldStyle), Behavior.attach(Validation))

The important part is not the amount of code. It is where responsibility lives:

component author  → publishes stable extension points
style author      → owns appearance
feature author    → owns element-level behavior
Foldkit app       → still owns state and transitions
resolver          → owns merge/conflict policy

Conflict rules are part of the abstraction

Attachments are not concatenated and left to incidental renderer ordering. The resolver has one merge policy:

  • classes are additive and deduplicated;
  • inline styles merge per property;
  • an event or scalar attribute has one owner;
  • a second owner produces a stable diagnostic instead of silently winning;
  • ChildAttribute values are preserved by identity;
  • Key and InnerHTML are structural and cannot be overridden;
  • multiple mounts compose into one OnMount.

This is what lets a design system and a feature attach independently without each one needing to know what the other did.

Slots are contracts, not DOM nodes

A slot is metadata about an extension point. It may declare:

  • a Capability such as Container, Interactive or TextInput;
  • published Event and Attr tokens;
  • platform Requirement tokens such as Keyboard, Pointer or Clipboard;
  • hidden points that are internal to the component;
  • protected events, attributes or style properties that attachments may not replace.

Capability is hierarchical, so a Behavior requiring Interactive can attach to a TextInput. Custom capabilities, events and attributes can be created with their corresponding .make constructors.