JavaScript Component Reference

Copy Markdown View Source

Quick reference guide for all JavaScript components in SaladUI, their states, events, and special features.

Core Classes

Component (core/component.js)

Base class for all interactive components.

Properties:

  • el - Root DOM element
  • hook - LiveView hook context
  • stateMachine - State machine instance
  • options - Parsed from data-options
  • allParts - Array of queryable parts

Methods:

  • getComponentConfig() - Override to define component behavior (must return a fresh object each call)
  • transition(event, params) - Trigger state transition
  • getPart(name) - Get single part by name
  • getAllParts(name) - Get all parts with name
  • getPartId(name) - Get or generate ID for part
  • pushEvent(event, data) - Send event to server
  • handleCommand(command, params) - Handle server commands
  • setupComponentEvents() - Override for custom event setup, called once by setupEvents()
  • teardownComponentEvents() - Override to undo setupComponentEvents(), called from removeAllEvents()
  • afterMount() - Override for logic that needs live listeners, called once right after setupEvents()
  • beforeDestroy() - Override for cleanup, called before listeners are removed
  • destroy() - Cleanup and remove listeners

See Component Lifecycle for the full contract between these hooks (which pair with which, and why).

StateMachine (core/state-machine.js)

State management with transitions.

Methods:

  • transition(event, params) - Execute transition
  • determineNextState(transition, params) - Resolve next state
  • executeTransition(prev, next, params) - Execute full transition
  • executeStateHandler(state, type, params) - Execute enter/exit handlers

Properties:

  • state - Current state
  • previousState - Previous state
  • stateConfig - State machine configuration

ComponentRegistry (core/factory.js)

Component registration and factory.

Methods:

  • register(type, ComponentClass) - Register component type
  • create(type, el, hookContext) - Create component instance

SaladUIHook (core/hook.js)

Phoenix LiveView hook for component integration.

Lifecycle:

  • mounted() - Initialize component
  • updated() - Reinitialize on DOM update
  • destroyed() - Cleanup component

Interactive Components

Accordion (components/accordion.js)

Collapsible sections with keyboard navigation.

States:

  • idle - Normal state

Options:

  • allowMultiple - Allow multiple items open (default: false)
  • defaultValue - Initially opened items

Events:

  • item-opened - Item was opened
  • item-closed - Item was closed

Parts:

  • root - Container
  • item - Accordion item
  • item-trigger - Item header/button
  • item-content - Item content panel

ARIA:

  • Triggers have role="button" and aria-expanded
  • Content has role="region" and aria-labelledby

Command (components/command.js)

Command palette with search and keyboard navigation.

States:

  • idle - Normal state
  • searching - User is searching

Options:

  • filter - Custom filter function
  • placeholder - Search placeholder text

Events:

  • select - Item was selected
  • search - Search query changed

Parts:

  • root - Container
  • input - Search input
  • list - Results list
  • item - Result item
  • empty - Empty state message

Keyboard:

  • - Navigate down
  • - Navigate up
  • Enter - Select item
  • Escape - Clear or close

Dialog (components/dialog.js)

Modal dialog with focus trap.

States:

  • closed - Dialog is closed
  • open - Dialog is open

Options:

  • closeOnOutsideClick - Close on overlay click (default: true)
  • animations - Animation configurations

Events:

  • open - Dialog opened
  • close - Dialog closed

Parts:

  • root - Container
  • trigger - Open button
  • content - Dialog container
  • content-panel - Dialog panel
  • overlay - Backdrop
  • close-trigger - Close button
  • title - Dialog title
  • description - Dialog description

Features:

  • Focus trap when open
  • Escape key to close
  • Click outside to close (optional)

ARIA:

  • role="dialog" on content
  • aria-modal="true" when open
  • aria-labelledby and aria-describedby

Context menu with keyboard navigation.

States:

  • closed - Menu closed
  • open - Menu open

Options:

  • positioning - Menu positioning config

Events:

  • open - Menu opened
  • close - Menu closed
  • select - Item selected

Parts:

  • root - Container
  • trigger - Menu trigger
  • content - Menu panel
  • item - Menu item
  • separator - Visual separator
  • label - Section label

Keyboard:

  • - Navigate down
  • - Navigate up
  • Enter/Space - Select item
  • Escape - Close menu

ARIA:

  • role="menu" on content
  • role="menuitem" on items
  • role="separator" on separators

HoverCard (components/hover-card.js)

Contextual information on hover.

States:

  • closed - Card hidden
  • open - Card visible

Options:

  • openDelay - Delay before opening (default: 200ms)
  • closeDelay - Delay before closing (default: 300ms)

Events:

  • open - Card opened
  • close - Card closed

Parts:

  • root - Container
  • trigger - Hover target
  • content - Card content panel

Features:

  • Delay timers for open/close
  • Mouse enter/leave detection
  • Positioning relative to trigger

Base menu component with navigation.

States:

  • closed - Menu closed
  • open - Menu open

Features:

  • Keyboard navigation (arrow keys)
  • Typeahead search
  • Nested submenus
  • Mouse and keyboard interaction

Parts:

  • root - Container
  • trigger - Menu trigger
  • content - Menu panel
  • item - Menu item
  • submenu - Nested submenu
  • separator - Visual divider

Popover (components/popover.js)

Floating content panel.

States:

  • closed - Popover closed
  • open - Popover open

Options:

  • positioning - Positioning configuration
  • closeOnOutsideClick - Close on outside click

Events:

  • open - Popover opened
  • close - Popover closed

Parts:

  • root - Container
  • trigger - Open button
  • content - Popover panel
  • close-trigger - Close button

Features:

  • Auto-positioning
  • Click outside detection
  • Escape to close

RadioGroup (components/radio_group.js)

Radio button group with keyboard navigation.

States:

  • idle - Normal state

Options:

  • defaultValue - Initially selected value
  • orientation - horizontal or vertical

Events:

  • value-changed - Selection changed

Parts:

  • root - Container
  • item - Radio button
  • indicator - Visual indicator

Keyboard:

  • / - Next item
  • / - Previous item
  • Space - Select item

ARIA:

  • role="radiogroup" on root
  • role="radio" on items
  • aria-checked on selected item

ScrollArea (components/scroll_area.js)

Custom scrollbar component.

Features:

  • Custom scrollbar styling
  • Horizontal and vertical scrolling
  • Auto-hide scrollbars

Parts:

  • root - Container
  • viewport - Scrollable area
  • scrollbar - Scrollbar track
  • thumb - Scrollbar thumb

Select (components/select.js)

Dropdown select with search and keyboard navigation.

States:

  • closed - Select closed
  • open - Select open

Options:

  • searchable - Enable search (default: false)
  • clearable - Show clear button (default: false)
  • multiple - Multiple selection (default: false)
  • placeholder - Placeholder text

Events:

  • open - Select opened
  • close - Select closed
  • value-changed - Selection changed
  • search - Search query changed

Parts:

  • root - Container
  • trigger - Select button
  • value - Selected value display
  • content - Options panel
  • search-input - Search input (if searchable)
  • item - Select option
  • clear-button - Clear button (if clearable)

Keyboard:

  • - Navigate down
  • - Navigate up
  • Enter/Space - Select item
  • Escape - Close
  • Type to search (if searchable)

ARIA:

  • role="combobox" on trigger
  • role="listbox" on content
  • role="option" on items
  • aria-selected on selected items

Slider (components/slider.js)

Range input slider.

States:

  • idle - Not being dragged
  • dragging - Currently dragging

Options:

  • min - Minimum value (default: 0)
  • max - Maximum value (default: 100)
  • step - Step increment (default: 1)
  • orientation - horizontal or vertical
  • multiple - Multiple thumbs

Events:

  • value-changed - Value changed
  • drag-start - Drag started
  • drag-end - Drag ended

Parts:

  • root - Container
  • track - Slider track
  • range - Filled range
  • thumb - Draggable handle

Keyboard:

  • / - Decrease value
  • / - Increase value
  • Home - Minimum value
  • End - Maximum value
  • PageUp/PageDown - Large increment

ARIA:

  • role="slider" on thumb
  • aria-valuemin, aria-valuemax, aria-valuenow
  • aria-orientation

Switch (components/switch.js)

Toggle switch component.

States:

  • unchecked - Switch off
  • checked - Switch on

Options:

  • defaultChecked - Initial state

Events:

  • checked - State changed

Parts:

  • root - Container
  • thumb - Switch thumb
  • input - Hidden input

Keyboard:

  • Space/Enter - Toggle

ARIA:

  • role="switch" on root
  • aria-checked reflects state

Tabs (components/tabs.js)

Tabbed content interface.

States:

  • Per tab: active or inactive

Options:

  • defaultValue - Initially active tab
  • orientation - horizontal or vertical

Events:

  • tab-changed - Active tab changed

Parts:

  • root - Container
  • list - Tab list container
  • trigger - Tab button
  • content - Tab panel

Keyboard:

  • / - Navigate tabs (horizontal)
  • / - Navigate tabs (vertical)
  • Home - First tab
  • End - Last tab

ARIA:

  • role="tablist" on list
  • role="tab" on triggers
  • role="tabpanel" on content
  • aria-selected on active tab
  • aria-controls links trigger to panel

Tooltip (components/tooltip.js)

Hover tooltip with delay.

States:

  • closed - Tooltip hidden
  • open - Tooltip visible

Options:

  • delay - Open delay (default: 200ms)
  • closeDelay - Close delay (default: 0ms)

Events:

  • open - Tooltip opened
  • close - Tooltip closed

Parts:

  • root - Container
  • trigger - Hover target
  • content - Tooltip content

Features:

  • Delay timers
  • Auto-positioning
  • Mouse tracking

ARIA:

  • role="tooltip" on content
  • aria-describedby on trigger

Collapsible (components/collapsible.js)

Expandable content panel.

States:

  • collapsed - Content hidden
  • expanded - Content visible

Options:

  • defaultOpen - Initially expanded

Events:

  • open - Content expanded
  • close - Content collapsed

Parts:

  • root - Container
  • trigger - Toggle button
  • content - Collapsible content

Keyboard:

  • Space/Enter - Toggle

ARIA:

  • role="button" on trigger
  • aria-expanded reflects state
  • aria-controls links trigger to content

Chart (components/chart.js)

Chart.js integration component.

States:

  • idle - Normal state
  • loading - Loading data

Options:

  • Chart.js configuration options

Events:

  • chart-created - Chart initialized
  • data-updated - Data refreshed

Commands:

  • update - Update chart data
  • resize - Resize chart
  • destroy - Destroy chart instance

Features:

  • Wraps Chart.js library
  • Dynamic data updates
  • Responsive sizing

Utility Classes

FocusTrap (core/focus-trap.js)

Traps focus within an element.

Methods:

  • activate() - Enable focus trap
  • deactivate() - Disable focus trap
  • destroy() - Cleanup

ClickOutsideMonitor (core/click-outside.js)

Detects clicks outside elements.

Constructor:

new ClickOutsideMonitor(elements, callback)

Methods:

  • start() - Start monitoring
  • stop() - Stop monitoring
  • destroy() - Cleanup

Positioning Utilities (core/utils.js)

Functions:

  • animateTransition(config, element) - Apply CSS transitions
  • queryDOM(root, filter) - Query DOM with filter function

Common Patterns

Basic Component Setup

import Component from "../core/component";
import SaladUI from "../index";

class MyComponent extends Component {
  constructor(el, hookContext) {
    super(el, { hookContext, initialState: "idle" });
  }

  getComponentConfig() {
    return {
      stateMachine: { /* ... */ },
      events: { /* ... */ },
      hiddenConfig: { /* ... */ },
      ariaConfig: { /* ... */ }
    };
  }
}

SaladUI.register("my-component", MyComponent);
export default MyComponent;

With Focus Trap

import FocusTrap from "../core/focus-trap";

onOpenEnter() {
  if (!this.focusTrap) {
    this.focusTrap = new FocusTrap(this.contentPanel);
  }
  this.focusTrap.activate();
}

onClosedEnter() {
  this.focusTrap?.deactivate();
}

beforeDestroy() {
  this.focusTrap?.destroy();
  this.focusTrap = null;
}

With Click Outside Detection

import ClickOutsideMonitor from "../core/click-outside";

setupComponentEvents() {
  super.setupComponentEvents();

  if (this.options.closeOnOutsideClick) {
    this.clickOutsideMonitor = new ClickOutsideMonitor(
      [this.contentPanel],
      () => this.transition("close")
    );
  }
}

// Pair teardown with setup — not beforeDestroy(). teardownComponentEvents()
// also runs if setupEvents() is ever re-invoked, so this is the only place
// guaranteed to undo what setupComponentEvents() created. See
// docs/component_lifecycle.md#common-pitfalls for what goes wrong if this
// cleanup lives in beforeDestroy() instead.
teardownComponentEvents() {
  super.teardownComponentEvents();
  this.clickOutsideMonitor?.destroy();
  this.clickOutsideMonitor = null;
}

onOpenEnter() {
  // start()/stop() just pause/resume the monitor — they don't create or
  // destroy it.
  this.clickOutsideMonitor?.start();
}

onClosedEnter() {
  this.clickOutsideMonitor?.stop();
}

With Keyboard Navigation

constructor(el, hookContext) {
  super(el, { hookContext });
  this.currentIndex = 0;
  this.items = [];
}

setupComponentEvents() {
  super.setupComponentEvents();
  this.items = this.getAllParts("item");
}

getEventConfig() {
  return {
    open: {
      keyMap: {
        ArrowDown: () => this.navigateNext(),
        ArrowUp: () => this.navigatePrev(),
        Enter: () => this.selectCurrent(),
        Escape: "close"
      }
    }
  };
}

navigateNext() {
  this.currentIndex = Math.min(
    this.currentIndex + 1,
    this.items.length - 1
  );
  this.focusCurrentItem();
}

navigatePrev() {
  this.currentIndex = Math.max(this.currentIndex - 1, 0);
  this.focusCurrentItem();
}

focusCurrentItem() {
  this.items[this.currentIndex]?.focus();
}

Testing Components

Manual Testing

Use the storybook application:

cd storybook
mix phx.server
# Visit http://localhost:4000

Console Testing

// In browser console
const dialog = document.querySelector("[data-component='dialog']");

// Access component instance (if exposed)
const component = dialog._saladui_component;

// Or send commands
dialog.dispatchEvent(new CustomEvent("salad_ui:command", {
  detail: { command: "open", params: {} }
}));

Debug Mode

Add logging to components:

onStateChanged(prev, next) {
  console.log(`State: ${prev} → ${next}`);
  return super.onStateChanged(prev, next);
}

transition(event, params) {
  console.log(`Transition: ${event}`, params);
  return super.transition(event, params);
}

Performance Tips

  1. Cache part references in constructor
  2. Use event delegation where possible
  3. Debounce expensive operations (search, positioning)
  4. Pair setup/teardown: undo setupComponentEvents() in teardownComponentEvents(), and other cleanup in beforeDestroy(), to prevent memory leaks
  5. Minimize DOM queries in event handlers
  6. Use requestAnimationFrame for animations
  7. Lazy initialize expensive features (focus trap, positioning)

Common Pitfalls

  1. Forgetting to register component - Always call SaladUI.register()
  2. Not cleaning up listeners - Anything created in setupComponentEvents() needs a matching teardownComponentEvents(); other cleanup goes in beforeDestroy()
  3. Querying parts too early - Parts may not be available in constructor
  4. Missing ARIA attributes - Define complete ariaConfig
  5. Calling setupEvents() yourself - It's called exactly once by the factory; use afterMount() if you need logic to run after listeners are live (see Component Lifecycle for the bug this caused historically)
  6. Not handling LiveView updates - Component is recreated on updates
  7. Circular event loops - Be careful with pushEvent triggering updates
  8. Missing error handling - Validate data in parseOptions()