# WildflowerJS
> WildflowerJS is a reactive JavaScript framework that uses standard HTML, CSS, and JavaScript without a build step, virtual DOM, or custom syntax. It provides reactivity through data attributes (`data-bind`, `data-action`, `data-list`, etc.) and direct DOM manipulation.
WildflowerJS was designed with a core philosophy: **trust the browser**. Instead of abstracting away the DOM with a virtual DOM layer, WildflowerJS works directly with native browser APIs that have decades of optimization.
## Core Concepts
- [Components](/docs/components): Component registration, state, computed properties, and lifecycle methods
- [State Management](/docs/state): Reactive state with automatic DOM updates
- [Data Binding](/docs/binding): One-way binding with `data-bind` attribute
- [Two-Way Binding](/docs/forms): Form handling with `data-model` attribute
- [Event Handling](/docs/events): Actions with `data-action` attribute
- [Lists](/docs/lists): Array rendering with `data-list` and templates
- [Conditionals](/docs/conditionals): `data-show` (CSS toggle) and `data-render` (DOM insertion/removal)
- [Computed Properties](/docs/computed): Derived values that auto-update
- [Props](/docs/props): Parent-to-child data passing with `data-prop-*` attributes
- [Configurable Templates](/docs/configurable-templates): Parent-defined templates for child rendering
## API Reference
- [HTML Attributes](/api/attributes): Complete reference for all data-* attributes
- [Component Definition](/api/component-definition): state, computed, props, types, watch, lifecycle hooks
- [Store API](/api/stores): Global state management with `store()` and `getStore()`
- [Router API](/api/router): Client-side routing with guards and nested routes
- [Application API](/api/application): Top-level framework methods and configuration
## Advanced Features
- [Advanced Lists](/docs/advanced-lists): Filtering, sorting, external data, and performance optimization
- [Stores](/docs/basic-stores): Global reactive state shared across components
- [Advanced Stores](/docs/advanced-stores): Store-to-store communication, persistence, API reference
- [Routing](/docs/routing): Hash and history mode client-side routing
- [SSR](/docs/ssr): Server-side rendering with hydration
- [Portals](/docs/portals): Render content outside component hierarchy
- [Transitions](/docs/transitions): CSS transition integration
- [Plugins](/docs/basic-plugins): Extend framework functionality
- [TypeScript](/docs/typescript): Full TypeScript support with type definitions
- [CSS Patterns](/docs/css-patterns): Component-scoped CSS without build tools
## Quick Start
```html
Count: 0
```
## Optimal Page Structure (Performance)
**CRITICAL:** Always place scripts in `` with `defer` for best performance:
```html
My App
```
**Why `defer` in `` is best:**
- Scripts download in parallel with HTML parsing (not sequentially)
- Execution order is preserved (framework loads before app.js)
- No `DOMContentLoaded` wrapper needed for external scripts
- Results in ~50% faster FCP and ~35% lower TBT vs scripts at end of body
- Lighthouse score improvement of ~15 points
**When an AI assistant generates a WildflowerJS app from a UAM manifest + idiom file, this structure is produced automatically.**
**Preventing Flash of Unstyled Content (FOUC):**
When using `defer` scripts, add this CSS to prevent modals from briefly flashing:
```css
/* Hide cloaked elements until framework processes them */
[data-cloak] { display: none; }
```
Add the `data-cloak` attribute to elements that should be hidden until the framework initializes (e.g., `data-show` elements starting false, `data-portal` elements). The framework removes `data-cloak` after initialization.
**Inline Scripts with Deferred Dependencies:**
If inline scripts depend on deferred libraries, wrap them in `DOMContentLoaded`:
```javascript
document.addEventListener('DOMContentLoaded', function() {
// Deferred scripts are now loaded
externalLibrary.init();
});
```
AI-generated UAM pages handle both of these automatically.
## Component Definition Pattern
```javascript
wildflower.component('component-name', {
// Reactive state
state: {
property: 'value',
nested: { data: true }
},
// Derived values (cached, auto-update)
computed: {
derivedValue() {
return this.state.property.toUpperCase();
}
},
// Props from parent (via data-prop-* attributes)
props: ['propName'],
// Runtime type validation (dev builds)
types: {
property: 'string'
},
// Watch specific state changes
watch: {
property(newVal, oldVal) {
console.log('Changed:', oldVal, '->', newVal);
},
// :immediate runs handler on init with current value, then on changes
'theme:immediate'(newVal, oldVal) {
document.body.className = `theme-${newVal}`;
}
},
// Declarative store subscriptions (optional)
subscribe: {
'store-name': ['path1', 'path2']
},
// Lifecycle: after mount
init() {
console.log(this.element); // DOM element
console.log(this.state); // Reactive state
console.log(this.props); // Read-only props
},
// Lifecycle: before destroy
destroy() {
// Cleanup subscriptions, timers, etc.
},
// Lifecycle: after any state change (no parameters - use watchers for specifics)
onUpdate() {
// React to state changes
},
// Lifecycle: when subscribed store path changes
onStoreUpdate(storeName, path, newValue, oldValue) {
// React to store changes
},
// Custom methods (bound to component)
myMethod() {
this.state.property = 'new value';
}
});
```
**Reserved method names** (framework-driven; do not use for action handlers or helpers): `init`, `beforeInit`, `destroy`, `beforeDestroy`, `onUpdate`, `beforeUpdate`, `onError`, `tick`. Most common trap: `tick` runs every animation frame for components in the pool loop, not on click.
**Actions before init complete** are queued and replayed in order after `init()` returns. Handlers can assume init-set state is present when they execute. Replayed actions see the original event arg, but `event.preventDefault()` is a no-op by replay time; for forms that must reliably block submission, use `data-event-prevent` on the form element.
**`wildflower.destroyComponent(id)` alone does NOT prevent re-initialization.** The framework treats any element with a stale `data-component-id` (instance no longer in `componentInstances`) as a fresh component pending initialization. On the next scan — triggered by any DOM mutation or explicit `wildflower.scan()` — the stale id is stripped, a new instance is created, and `init()` fires again. By design (lets third-party HTML caches like DataTables replay cached DOM). To truly tear down, remove the element from the DOM AND destroy the instance:
```javascript
// BAD: auto-resurrect can re-init
wildflower.destroyComponent(instance.id);
// GOOD: both must happen, either order
instance.element.remove();
wildflower.destroyComponent(instance.id);
```
For the common case (removing UI), you usually don't need `destroyComponent` at all — just remove the element and the framework cleans up via its mutation observer.
## DOM Helpers (this.$el)
jQuery-like DOM manipulation scoped to the component element. Events are auto-cleaned on component destroy.
```javascript
wildflower.component('my-component', {
state: { count: 0 },
init() {
// Select and chain operations
this.$el('.message')
.addClass('highlight')
.css({ color: 'blue', fontWeight: 'bold' })
.text('Updated!');
// Get raw DOM element for third-party libraries
const input = this.$el('.date-input').el; // Returns Element or null
flatpickr(input, { dateFormat: 'Y-m-d' });
// Event binding (auto-cleanup when component is destroyed)
this.$el('.btn').on('click', () => {
this.state.count++;
});
// Iterate over multiple elements
this.$el('.item').each((el, index) => {
console.log(`Item ${index}:`, el.textContent);
});
// Form value with reactivity bridge (triggers data-model sync)
this.$el('input').val('new value'); // Dispatches input event
}
});
```
**Selection:**
| Method | Returns | Description |
|--------|---------|-------------|
| `this.$el(selector)` | Wrapper | Select elements within component |
| `this.$el()` | Wrapper | Select component root element |
| `this.$el(element)` | Wrapper | Wrap raw DOM element |
**Element Access:**
| Method | Returns | Description |
|--------|---------|-------------|
| `.el` | Element/null | First raw DOM element (for third-party libs) |
| `.get(i)` | Element | Element at index |
| `.length` | Number | Count of matched elements |
| `.each(fn)` | Wrapper | Iterate with `fn(el, index)`, `this` = component |
**Manipulation (all chainable):**
| Method | Description |
|--------|-------------|
| `.addClass(names)` | Add space-separated classes |
| `.removeClass(names)` | Remove classes |
| `.toggleClass(name)` | Toggle class |
| `.css(prop, val)` | Set style |
| `.css({...})` | Set multiple styles |
| `.attr(name, val)` | Set attribute |
| `.text(val)` | Set text content |
| `.html(val)` | Set HTML (auto-scans for components) |
| `.val(val)` | Set input value (triggers input event for data-model) |
| `.show()` / `.hide()` | Toggle display |
**Events:**
| Method | Description |
|--------|-------------|
| `.on(event, fn)` | Attach handler (auto-cleanup on destroy) |
| `.off(event, fn?)` | Remove handler(s) |
| `.trigger(event)` | Dispatch custom event |
**Traversal (boundary-enforced - can't escape component):**
| Method | Description |
|--------|-------------|
| `.find(sel)` | Find descendants |
| `.parent()` | Parent element (within component) |
| `.closest(sel)` | Closest ancestor (within component) |
| `.children()` | Direct children |
## Primitive Lists ($this)
For lists of strings, numbers, or other primitives, use `$this` to bind the item value:
```html
```
`$this` refers to the current item itself. For object arrays, use property names directly (e.g., `data-bind="name"`).
## Components in Lists (this.listItem)
When a component is rendered inside a `data-list` template, it can access the list item data via `this.listItem`:
```html
...
```
```javascript
wildflower.component('task-card', {
state: {
taskId: null
},
beforeInit() {
// this.listItem contains the task object for this list item
if (this.listItem) {
this.state.taskId = this.listItem.id;
}
}
});
```
**Key points:**
- `this.listItem` is available in `beforeInit()`, `init()`, and all lifecycle methods
- Returns `null` for components not in a list
- Provides a live reference to the data object
- For nested lists, returns the immediate parent list's item, not an ancestor's
- Component boundaries block inheritance: inner components don't inherit outer component's `listItem`
## CSS Patterns (No Build Step)
WildflowerJS is CSS-agnostic. For component-scoped styles without build tools, use the `:where()` pattern:
```css
/* Zero-specificity scoping using data-component attribute */
:where([data-component="kanban-column"]) {
.header {
font-weight: bold;
padding: 1rem;
}
.card {
border-radius: 4px;
margin-bottom: 0.5rem;
}
}
```
**Why `:where()`?**
- Zero specificity: Global themes/utilities can still override easily
- Zero framework overhead: Browser handles it natively, no JS processing
- Modern browser support: Chrome 88+, Firefox 78+, Safari 14+
**CSS + Reactive Styles:**
Use CSS for static structure, `data-bind-style` for dynamic values:
```html
```
```css
:where([data-component="kanban-column"]) {
.header {
padding: 1rem;
border-radius: 8px;
/* backgroundColor comes from data-bind-style */
}
}
```
## HTML Attributes Summary
| Attribute | Purpose | Example |
|-----------|---------|---------|
| `data-component` | Define component root | `
` |
| `data-bind` | Bind text content | `` |
| `data-bind="computedName"` | Bind to computed (auto-detected, no prefix needed) | `` |
| `data-bind-html` | Bind HTML content (XSS risk: use `setHtmlSanitizer()` with untrusted input) | `