diff --git a/BUTTON_FORM_EXPLORATION.md b/BUTTON_FORM_EXPLORATION.md
new file mode 100644
index 0000000..2c857fd
--- /dev/null
+++ b/BUTTON_FORM_EXPLORATION.md
@@ -0,0 +1,634 @@
+# Button Tag and Form Elements - Exploration
+
+This document explores the possibilities for supporting button tags and form elements in Treebark, taking inspiration from [AdaptiveCards actions pattern](https://learn.microsoft.com/en-us/adaptive-cards/sdk/rendering-cards/javascript/actions).
+
+## Executive Summary
+
+**Key Design Insight:** Use the **name-value pair pattern** for interactive elements:
+- **Buttons** return a single name-value pair: `(name: string, value: string)`
+- **Forms** return an array of name-value pairs: `Array<[name, value]>` (via FormData)
+
+This pattern:
+- ✅ Aligns with HTML semantics (input elements already use name/value)
+- ✅ Simple and intuitive API
+- ✅ Provides natural foundation for form support
+- ✅ No JSON parsing or complex payload extraction needed
+- ✅ Type-safe and easy to test
+
+**Recommended Approach:** Start with Path 2 (Button with Name-Value Handler), optionally expand to Path 3 (Full Form Support) if use cases emerge.
+
+## Problem Statement
+
+Treebark currently blocks interactive elements like `button`, `input`, `select`, `textarea`, and `form` for security reasons. However, there may be use cases where safe, template-driven interactive elements could be valuable, especially in content-driven applications.
+
+## Key Questions to Explore
+
+1. **Should we support interactive elements at all?** What's the use case?
+2. **If yes, how do we maintain security?** No arbitrary JavaScript execution
+3. **How would event handling work?** Context-based handlers vs. inline handlers
+4. **What's the API surface?** Minimal and intuitive
+5. **DOM-only or both renderers?** String renderer limitations
+
+## Exploration 1: Button Tag with Context-Based Actions
+
+### Design Principle: Name-Value Pairs
+
+**Key Insight:** Buttons should return a **name:value pair** when clicked. This simple pattern:
+- Aligns with HTML form semantics (input elements have name/value)
+- Provides a natural foundation for form support
+- Keeps the API minimal and intuitive
+
+### AdaptiveCards Pattern Reference
+
+AdaptiveCards uses a pattern where:
+- Actions are defined in the template with a `type` and `data`
+- A single action handler is registered for the entire card context
+- The handler receives the action type and data when triggered
+
+Example from AdaptiveCards:
+```json
+{
+ "type": "Action.Submit",
+ "title": "Save",
+ "data": {
+ "action": "save",
+ "id": "123"
+ }
+}
+```
+
+### Proposed Treebark Pattern: Name-Value Pairs
+
+#### Option A: Single Context Handler with Name-Value Pairs
+
+Template defines buttons with `name` and `value` attributes:
+```javascript
+{
+ template: {
+ div: {
+ $children: [
+ {
+ button: {
+ type: "button",
+ name: "action",
+ value: "save",
+ $children: ["Save"]
+ }
+ },
+ {
+ button: {
+ type: "button",
+ name: "action",
+ value: "delete",
+ $children: ["Delete"]
+ }
+ },
+ {
+ button: {
+ type: "button",
+ name: "itemId",
+ value: "123",
+ $children: ["Select Item 123"]
+ }
+ }
+ ]
+ }
+ },
+ data: { /* template data */ }
+}
+```
+
+Application provides a single handler that receives name-value pairs:
+```javascript
+const fragment = renderToDOM(input, {
+ onAction: (name, value, event) => {
+ // Receives: name (string), value (string), event (MouseEvent)
+ console.log(`Button clicked: ${name}=${value}`);
+
+ if (name === 'action') {
+ switch(value) {
+ case 'save':
+ console.log('Save action triggered');
+ break;
+ case 'delete':
+ console.log('Delete action triggered');
+ break;
+ }
+ } else if (name === 'itemId') {
+ console.log(`Item ${value} selected`);
+ }
+ }
+});
+```
+
+**Benefits of Name-Value Pattern:**
+- **Familiar:** Mirrors HTML form input pattern (``)
+- **Simple:** Just two strings - easy to understand and use
+- **Flexible:** Can represent actions, IDs, or any semantic data
+- **Form-ready:** Same pattern works for form inputs (see below)
+- **Type-safe:** Both name and value are always strings
+
+**Pros:**
+- Clean separation: templates define structure, app defines behavior
+- Single handler reduces boilerplate
+- Natural extension to forms
+- Aligns with HTML semantics
+- No need for JSON parsing or complex payload extraction
+
+**Cons:**
+- Requires DOM renderer (won't work in string renderer)
+- Additional API surface in RenderOptions
+- Limited to string values (but can use JSON.stringify if needed)
+
+#### Option B: External Handler Attachment (Current HTML standard)
+
+Template only defines structure:
+```javascript
+{
+ template: {
+ button: {
+ class: "btn-save",
+ "data-action": "save",
+ "data-id": "123",
+ $children: ["Save"]
+ }
+ }
+}
+```
+
+Application attaches handlers after rendering:
+```javascript
+const fragment = renderToDOM(input);
+document.body.appendChild(fragment);
+
+// App adds handlers externally
+document.querySelectorAll('button').forEach(btn => {
+ btn.addEventListener('click', (e) => {
+ const action = e.target.getAttribute('data-action');
+ const id = e.target.getAttribute('data-id');
+ handleAction(action, { id });
+ });
+});
+```
+
+**Pros:**
+- Simplest implementation (just allow the tag)
+- No new API surface
+- Works with standard DOM APIs
+- Clearest security model
+
+**Cons:**
+- More boilerplate in application code
+- Not as elegant as declarative actions
+
+#### Option C: Hybrid - Optional Context Handler
+
+Allow button tag always, but provide optional context handler:
+```javascript
+// Without handler - external attachment needed
+const fragment1 = renderToDOM({ template: { button: "Click" } });
+
+// With optional handler
+const fragment2 = renderToDOM(
+ { template: { button: { "data-action": "save", $children: ["Save"] } } },
+ {
+ onAction: (action, payload) => { /* handle */ }
+ }
+);
+```
+
+**Pros:**
+- Flexibility for different use cases
+- Progressive enhancement
+- Doesn't force a pattern
+
+**Cons:**
+- Two ways to do the same thing
+- More complex to document and maintain
+
+### Additional Data with Buttons
+
+If buttons need to carry additional data beyond name-value:
+
+**Option 1: Use data attributes (read but not sent):**
+```javascript
+{
+ button: {
+ name: "action",
+ value: "delete",
+ "data-item-id": "123",
+ "data-confirm": "true",
+ $children: ["Delete Item 123"]
+ }
+}
+
+// Handler can access via event.target
+onAction: (name, value, event) => {
+ if (name === 'action' && value === 'delete') {
+ const itemId = event.target.getAttribute('data-item-id');
+ const needsConfirm = event.target.getAttribute('data-confirm');
+ // Handle deletion...
+ }
+}
+```
+
+**Option 2: Encode in value (if needed):**
+```javascript
+{
+ button: {
+ name: "action",
+ value: JSON.stringify({ type: "delete", itemId: 123 }),
+ $children: ["Delete"]
+ }
+}
+
+// Handler parses if needed
+onAction: (name, value, event) => {
+ if (name === 'action') {
+ const action = JSON.parse(value);
+ // action.type, action.itemId...
+ }
+}
+```
+
+## Exploration 2: Form Elements with Name-Value Pairs
+
+### Which Form Elements?
+
+If we allow buttons, should we also allow:
+- `input` (text, checkbox, radio, etc.)
+- `textarea`
+- `select` / `option`
+- `label`
+- `form` (container)
+
+### Security Considerations
+
+**Safe:**
+- `button` - can only trigger actions
+- `label` - just associates with inputs
+- Read-only inputs with `readonly` attribute
+
+**Potentially risky:**
+- `form` with `action` attribute (server-side submission)
+- File inputs (`input type="file"`)
+- Hidden inputs that could be manipulated
+
+### Key Insight: Forms Return Array of Name-Value Pairs
+
+Building on the button pattern, **forms should return an array of name-value pairs** - exactly like HTML's FormData.
+
+### Possible Form Pattern
+
+#### Read-only Forms (Display Only)
+
+```javascript
+{
+ template: {
+ form: {
+ $children: [
+ { label: { for: "name", $children: ["Name:"] } },
+ { input: { type: "text", id: "name", name: "name", readonly: "true", value: "{{userName}}" } },
+
+ { label: { for: "email", $children: ["Email:"] } },
+ { input: { type: "email", id: "email", name: "email", readonly: "true", value: "{{userEmail}}" } }
+ ]
+ }
+ },
+ data: { userName: "Alice", userEmail: "alice@example.com" }
+}
+```
+
+**Use case:** Displaying form-like data (receipts, confirmations, etc.)
+
+#### Interactive Forms with Context Handler
+
+Template with form elements (each has `name` attribute):
+```javascript
+{
+ template: {
+ form: {
+ $children: [
+ { label: { for: "name", $children: ["Name:"] } },
+ { input: { type: "text", id: "name", name: "name", value: "{{userName}}" } },
+
+ { label: { for: "email", $children: ["Email:"] } },
+ { input: { type: "email", id: "email", name: "email", value: "{{userEmail}}" } },
+
+ { label: { for: "role", $children: ["Role:"] } },
+ {
+ select: {
+ id: "role",
+ name: "role",
+ $children: [
+ { option: { value: "user", $children: ["User"] } },
+ { option: { value: "admin", selected: "true", $children: ["Admin"] } }
+ ]
+ }
+ },
+
+ { button: { type: "submit", name: "action", value: "save", $children: ["Save"] } }
+ ]
+ }
+ },
+ data: { userName: "Alice", userEmail: "alice@example.com" }
+}
+```
+
+Handler receives array of name-value pairs:
+```javascript
+const fragment = renderToDOM(input, {
+ onAction: (name, value, event) => {
+ if (name === 'action' && value === 'save') {
+ // Get form data as array of [name, value] pairs
+ const form = event.target.closest('form');
+ const formData = new FormData(form);
+ const formValues = Array.from(formData.entries()); // [["name", "Alice"], ["email", "alice@..."], ["role", "admin"]]
+
+ console.log('Form submitted with values:', formValues);
+
+ // Or convert to object if preferred
+ const formObject = Object.fromEntries(formValues);
+ console.log('As object:', formObject); // { name: "Alice", email: "alice@...", role: "admin" }
+ }
+ }
+});
+```
+
+**Benefits of Array of Name-Value Pairs:**
+- **Standard HTML:** Exactly how FormData works
+- **Consistent:** Same pattern as button (single name-value pair)
+- **Multiple values:** Supports multiple inputs with same name (checkboxes, multi-select)
+- **Preserves order:** Array maintains form field order
+- **Simple:** No parsing needed, just extract from FormData
+
+**Alternative: Provide helper in handler:**
+```javascript
+onAction: (name, value, event) => {
+ if (name === 'action' && value === 'save') {
+ // Helper function extracts form data
+ const formValues = getFormValues(event.target);
+ // formValues is array: [["name", "Alice"], ["email", "..."], ...]
+ }
+}
+
+// Could be provided as utility
+function getFormValues(button) {
+ const form = button.closest('form');
+ return Array.from(new FormData(form).entries());
+}
+```
+
+**Use case:** Simple data collection forms in content
+
+### Form Element Attributes
+
+Which attributes should be allowed?
+
+**Definitely safe:**
+- `type`, `id`, `name`, `value`, `placeholder`
+- `readonly`, `disabled`
+- `for` (on labels)
+- `rows`, `cols` (on textarea)
+- `checked` (on checkbox/radio)
+- `selected` (on option)
+- `multiple` (on select)
+
+**Questionable:**
+- `action` (on form) - could submit to arbitrary URLs
+- `method` (on form) - GET vs POST
+- `autocomplete` - privacy implications?
+
+**Definitely block:**
+- `formaction`, `formmethod` - can override form behavior
+- `onfocus`, `onblur`, `onchange` - event handlers
+
+## Implementation Considerations
+
+### 1. Tag Whitelist Updates
+
+```javascript
+// In common.ts
+export const CONTAINER_TAGS = new Set([
+ // ... existing tags ...
+ 'button',
+ 'form',
+ 'label'
+]);
+
+export const VOID_TAGS = new Set([
+ // ... existing tags ...
+ 'input'
+]);
+
+// Conditionally allowed (needs discussion)
+export const FORM_TAGS = new Set([
+ 'select',
+ 'option',
+ 'textarea'
+]);
+```
+
+### 2. Attribute Whitelist Updates
+
+```javascript
+export const TAG_SPECIFIC_ATTRS: Record> = {
+ // ... existing ...
+ 'button': new Set(['type', 'disabled', 'name', 'value']), // name and value for action pattern
+ 'input': new Set(['type', 'name', 'value', 'placeholder', 'readonly', 'disabled', 'checked']),
+ 'textarea': new Set(['name', 'rows', 'cols', 'placeholder', 'readonly', 'disabled']),
+ 'select': new Set(['name', 'multiple', 'disabled']),
+ 'option': new Set(['value', 'selected']),
+ 'label': new Set(['for']),
+ 'form': new Set(['data-form-id']) // Explicitly NOT action/method
+};
+```
+
+### 3. Context Handler API with Name-Value Pairs
+
+```typescript
+interface RenderOptions {
+ indent?: string | number | boolean;
+ logger?: Logger;
+ onAction?: ActionHandler; // NEW - receives name-value pairs
+}
+
+type ActionHandler = (name: string, value: string, event: MouseEvent) => void;
+```
+
+**Simple and aligned with HTML semantics:**
+- `name`: The button's `name` attribute
+- `value`: The button's `value` attribute
+- `event`: The click event (access to target element, form context, etc.)
+
+### 4. DOM Renderer Changes
+
+```javascript
+function setAttrs(element: HTMLElement, attrs: Record, data: Data, tag: string, parents: Data[] = [], logger: Logger, onAction?: ActionHandler): void {
+ // ... existing attribute handling ...
+
+ // If button and onAction handler provided
+ if (tag === 'button' && onAction) {
+ element.addEventListener('click', (event: MouseEvent) => {
+ event.preventDefault();
+
+ const nameAttr = element.getAttribute('name');
+ const valueAttr = element.getAttribute('value');
+
+ if (!nameAttr) {
+ logger.warn('Button with onAction handler should have a name attribute');
+ return;
+ }
+
+ // Call handler with name-value pair
+ // Value defaults to empty string if not provided
+ onAction(nameAttr, valueAttr || '', event);
+ });
+ }
+}
+```
+
+**Benefits:**
+- Simple implementation - just extract two attributes
+- No JSON parsing or complex payload logic
+- Aligns perfectly with HTML button semantics
+- Easy to test and maintain
+
+## Recommendations
+
+Based on this exploration with **name-value pair pattern**, here are potential paths forward:
+
+### Path 1: Minimal - Button Tag Only (No Handler)
+
+**Implementation:**
+- Allow `button` tag in whitelist
+- Support `type`, `disabled`, `name`, `value` attributes
+- No special event handling - apps attach handlers externally
+- Document best practices for using name/value attributes
+
+**Pros:** Minimal change, clear security model, easy to understand
+**Cons:** Doesn't provide much value over current workarounds
+
+### Path 2: Button with Name-Value Handler (Recommended)
+
+**Implementation:**
+- Allow `button` tag in whitelist with `name` and `value` attributes
+- Add optional `onAction: (name, value, event) => void` to RenderOptions (DOM only)
+- Auto-wire buttons that have `name` attribute to the handler
+- Simple pattern: button returns name-value pair when clicked
+
+**Example:**
+```javascript
+// Template
+{ button: { name: "action", value: "save", $children: ["Save"] } }
+
+// Handler
+renderToDOM(input, {
+ onAction: (name, value, event) => {
+ console.log(`${name}=${value}`); // "action=save"
+ }
+});
+```
+
+**Pros:**
+- Elegant and simple
+- Aligns with HTML semantics
+- Natural foundation for forms
+- Optional - works without handler too
+
+**Cons:**
+- DOM-only feature
+- New API to maintain
+
+### Path 3: Full Form Support with Name-Value Pattern
+
+**Implementation:**
+- Allow button, input, select, textarea, label, form
+- All form elements use `name` attribute
+- Button returns single name-value pair
+- Forms return array of name-value pairs via FormData
+- Strict attribute whitelisting (no action/method on form)
+- Optional context handler using same pattern
+
+**Example:**
+```javascript
+// Template with form
+{
+ form: {
+ $children: [
+ { input: { type: "text", name: "username", value: "{{user}}" } },
+ { button: { type: "submit", name: "action", value: "save", $children: ["Save"] } }
+ ]
+ }
+}
+
+// Handler
+onAction: (name, value, event) => {
+ if (name === 'action' && value === 'save') {
+ const form = event.target.closest('form');
+ const formData = Array.from(new FormData(form).entries());
+ // formData = [["username", "alice"], ["action", "save"]]
+ }
+}
+```
+
+**Pros:**
+- Powerful and consistent pattern
+- Works for simple and complex forms
+- Standard HTML FormData integration
+
+**Cons:**
+- Larger API surface
+- Need to carefully consider security
+- More complexity
+
+### Path 4: No Changes
+
+**Implementation:**
+- Keep current blocking of interactive elements
+- Document workarounds (render content, attach handlers externally)
+
+**Pros:** Zero risk, zero maintenance
+**Cons:** Less useful for interactive content scenarios
+
+## Questions for Discussion
+
+1. **What's the actual use case?** Is this for CMS content with occasional buttons, or for building full form-based UIs?
+
+2. **DOM-only acceptable?** If features only work in DOM renderer, is that okay?
+
+3. **Security vs. convenience tradeoff?** Where do we draw the line on what's allowed?
+
+4. **Maintenance burden?** How much API surface are we willing to support long-term?
+
+5. **Alternative approaches?** Could we provide helper utilities instead of built-in support?
+
+## Next Steps
+
+To move this forward, we need to:
+
+1. **Define the use case clearly** - What problem are we actually solving?
+2. **Choose a path** - Which approach aligns with Treebark's goals?
+3. **Prototype** - Build a minimal proof-of-concept
+4. **Test with real content** - Does it work for actual use cases?
+5. **Document** - Clear security model and best practices
+6. **Decide** - Ship it, iterate, or abandon?
+
+## Appendix: AdaptiveCards Reference
+
+AdaptiveCards action types:
+- `Action.OpenUrl` - Opens a URL
+- `Action.Submit` - Submits data to the host app
+- `Action.ShowCard` - Shows another card
+- `Action.ToggleVisibility` - Shows/hides elements
+
+Their pattern:
+```javascript
+adaptiveCard.onExecuteAction = function(action) {
+ if (action instanceof AC.SubmitAction) {
+ console.log("Submitted data:", action.data);
+ }
+}
+```
+
+This is similar to our proposed `onAction` handler pattern.