The enhancement is yours

Actual provides the lifecycle; applications register their own behaviors. This is the same API tabs and scrollspy use — the registry finds existing elements, wires injected ones, and calls the cleanup when an element leaves the document.

import { registerEnhancement } from
  "https://cdn.jsdelivr.net/npm/actual-css@0.7.0/src/js/enhance.js";
// with the npm package:
// import { registerEnhancement } from "actual-css/js/enhance";

registerEnhancement("select-intent", (select) => {
  if (!(select instanceof HTMLSelectElement) || select.multiple) return;

  const sync = () => {
    const previous = select.dataset.currentIntent;
    if (previous) select.classList.remove(previous);

    const intent = select.selectedOptions[0]?.dataset.intent;

    if (intent) {
      select.classList.add(intent);
      select.dataset.currentIntent = intent;
    } else {
      delete select.dataset.currentIntent;
    }
  };

  const controller = new AbortController();

  sync();
  select.addEventListener("change", sync, { signal: controller.signal });

  return () => controller.abort();
});

The module knows nothing about primary/success/warning/danger. It mirrors the selected option's data-intent onto the control and remembers the class it added (data-current-intent), removing it on the next change. An option without data-intent clears back to a plain control.

primary warning success danger

Semantics on the option

data-intent describes the option; the enhancement relays it onto the control. Preselects ship their intent class in the markup, so they paint before the module even loads — the enhancement adopts them at init.

Issue status Preselected Done; its success class ships in the markup.
Priority — optgroups work too

Any select size

The recipe rides the existing .sm/.lg size roles and the control tokens — nothing to restyle per size.

Compact
Base
Large

Injected markup, no extra code

registerEnhancement observes the document. A fresh <select data-enhance="select-intent"> appended below is wired automatically — the attribute alone is the whole contract.

Watch the propagation

The enhancement only ever touches the currentIntent class it owns. Change the status select above and the readout follows.

A JS value/selectedIndex change must dispatch change.

Opt-in only

Without the data-enhance="select-intent" token, this identical select keeps its baseline look no matter which option is chosen — data-intent on options is meaning, not paint.

Same options, no token