# Forms And Inputs

> A Claude skill from deeleeramone/PyWry.

- **Type:** Skill
- **Install:** `agentstack add skill-deeleeramone-pywry-forms-and-inputs`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [deeleeramone](https://agentstack.voostack.com/s/deeleeramone)
- **Installs:** 0
- **Category:** [Data & Analytics](https://agentstack.voostack.com/c/data-and-analytics)
- **Latest version:** 0.1.0
- **License:** Apache-2.0
- **Upstream author:** [deeleeramone](https://github.com/deeleeramone)
- **Source:** https://github.com/deeleeramone/PyWry/tree/develop/pywry/pywry/mcp/skills/forms_and_inputs
- **Website:** https://deeleeramone.github.io/PyWry/

## Install

```sh
agentstack add skill-deeleeramone-pywry-forms-and-inputs
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Forms and User Input

> Building interactive forms with validation and collecting user input.

## Available Input Components

| Type | Component | Use Case |
|------|-----------|----------|
| `text` | TextInput | Single-line text (names, emails) |
| `textarea` | TextareaInput | Multi-line text (comments, descriptions) |
| `number` | NumberInput | Numeric values with min/max/step |
| `date` | DateInput | Date selection |
| `search` | SearchInput | Search with debounce |
| `secret` | SecretInput | Passwords, API keys (value hidden) |
| `select` | Select | Single choice dropdown |
| `multiselect` | MultiSelect | Multiple choices |
| `radio` | RadioGroup | Visible single choice (small set) |
| `checkbox` | Checkbox | Boolean toggle |
| `toggle` | Toggle | On/off switch |
| `slider` | SliderInput | Single value in range |
| `range` | RangeInput | Two values (min/max range) |

## Basic Form Pattern

```python
create_widget(
    html='Fill out the form and click Submit',
    toolbars=[{
        "position": "left",
        "items": [
            {"type": "text", "label": "Name", "event": "form:name", "placeholder": "Enter your name"},
            {"type": "text", "label": "Email", "event": "form:email", "placeholder": "email@example.com"},
            {"type": "number", "label": "Age", "event": "form:age", "min": 0, "max": 120, "value": 25},
            {"type": "select", "label": "Country", "event": "form:country",
             "options": [
                 {"label": "United States", "value": "us"},
                 {"label": "United Kingdom", "value": "uk"},
                 {"label": "Canada", "value": "ca"},
             ]},
            {"type": "button", "label": "Submit", "event": "form:submit", "variant": "primary"},
        ]
    }]
)
```

## Collecting Form Data

### From Events
```python
events = get_events(widget_id, clear=True)

# Collect latest values
form_data = {}
for e in events:
    if e["event_type"].startswith("form:"):
        field = e["event_type"].replace("form:", "")
        form_data[field] = e["data"].get("value")

# On submit
if "submit" in form_data:
    process_form(form_data)
```

### Tracking State
```python
# Keep running state across event polls
form_state = {}

while True:
    events = get_events(widget_id, clear=True)
    for e in events:
        if e["event_type"].startswith("form:"):
            field = e["event_type"].replace("form:", "")
            if field != "submit":
                form_state[field] = e["data"].get("value")
            else:
                # Submit with current state
                result = process_form(form_state)
                set_content(widget_id, component_id="results", text=result)
    time.sleep(0.1)
```

## Validation Patterns

### Show Error Toast
```python
def validate_form(data):
    if not data.get("name"):
        show_toast(widget_id, message="Name is required", type="error")
        return False
    if not data.get("email") or "@" not in data["email"]:
        show_toast(widget_id, message="Valid email is required", type="error")
        return False
    return True
```

### Highlight Invalid Fields
```python
def highlight_error(component_id):
    set_style(widget_id, component_id=component_id, styles={
        "borderColor": "var(--error-color)",
        "boxShadow": "0 0 0 2px rgba(239, 68, 68, 0.2)",
    })

def clear_error(component_id):
    set_style(widget_id, component_id=component_id, styles={
        "borderColor": "var(--border-color)",
        "boxShadow": "none",
    })
```

### Show Inline Errors
```python
# Create form with error placeholders
html = """

    

"""

# Show error
set_content(widget_id, component_id="name-error", text="Name is required")
set_style(widget_id, component_id="name-error", styles={"display": "block"})

# Clear error
set_style(widget_id, component_id="name-error", styles={"display": "none"})
```

## Input Component Details

### Text Input
```python
{"type": "text",
 "label": "Username",
 "event": "username",
 "placeholder": "Enter username",
 "value": "",  # Default value
}
# Event data: {"value": "entered_text"}
```

### Number Input
```python
{"type": "number",
 "label": "Quantity",
 "event": "quantity",
 "min": 1,
 "max": 100,
 "step": 1,
 "value": 10,
}
# Event data: {"value": 10}  # Always a number
```

### Select
```python
{"type": "select",
 "label": "Category",
 "event": "category",
 "options": [
     {"label": "Option A", "value": "a"},
     {"label": "Option B", "value": "b"},
     {"label": "Option C", "value": "c"},
 ],
 "value": "a",  # Default selection
}
# Event data: {"value": "a", "label": "Option A"}
```

### MultiSelect
```python
{"type": "multiselect",
 "label": "Tags",
 "event": "tags",
 "options": [
     {"label": "Important", "value": "important"},
     {"label": "Urgent", "value": "urgent"},
     {"label": "Review", "value": "review"},
 ],
 "value": ["important"],  # Default selections (array)
}
# Event data: {"value": ["important", "urgent"]}
```

### Radio Group
```python
{"type": "radio",
 "label": "Priority",
 "event": "priority",
 "options": [
     {"label": "Low", "value": "low"},
     {"label": "Medium", "value": "medium"},
     {"label": "High", "value": "high"},
 ],
 "value": "medium",
}
# Event data: {"value": "medium"}
```

### Toggle / Checkbox
```python
{"type": "toggle",  # or "checkbox"
 "label": "Enable notifications",
 "event": "notifications",
 "value": True,  # Default state
}
# Event data: {"value": true}  # Boolean
```

### Slider
```python
{"type": "slider",
 "label": "Volume",
 "event": "volume",
 "min": 0,
 "max": 100,
 "step": 5,
 "value": 50,
}
# Event data: {"value": 50}
```

### Range (Two Values)
```python
{"type": "range",
 "label": "Price Range",
 "event": "price_range",
 "min": 0,
 "max": 1000,
 "step": 10,
 "value": [100, 500],  # [min_value, max_value]
}
# Event data: {"value": [100, 500]}
```

### Date
```python
{"type": "date",
 "label": "Start Date",
 "event": "start_date",
 "value": "2024-01-01",  # ISO format
}
# Event data: {"value": "2024-01-15"}
```

### Search (with debounce)
```python
{"type": "search",
 "label": "Search",
 "event": "search",
 "placeholder": "Type to search...",
 "debounce": 300,  # ms delay before firing event
}
# Event data: {"value": "search query"}
```

## Secret Input (Passwords / API Keys)

```python
{"type": "secret",
 "label": "API Key",
 "event": "api_key",
 "placeholder": "Enter your API key",
}

# Events emitted:
# "api_key" - Value changed (value in event data, but NEVER rendered in HTML)
# "api_key:reveal" - User clicked reveal button
# "api_key:copy" - User clicked copy button
```

### Handling Secret Events
```python
events = get_events(widget_id, clear=True)
for e in events:
    if e["event_type"] == "api_key":
        # Store securely - never log or display
        api_key = e["data"]["value"]
        save_api_key_securely(api_key)
    elif e["event_type"] == "api_key:reveal":
        # User wants to see the key - show toast or handle appropriately
        show_toast(widget_id, message="Key revealed in input", type="info")
    elif e["event_type"] == "api_key:copy":
        show_toast(widget_id, message="API key copied to clipboard", type="success")
```

## Complete Form Example

```python
from pywry import PyWry
from pywry.toolbar import Div, Button, Toolbar

app = PyWry()

content = Div(
    content="""
        
            Registration Form
            
        
    """,
    component_id="form-container",
)

widget = app.show(
    html=content.build_html(),
    height=500,
    toolbars=[
        Toolbar(
            position="left",
            items=[
                {"type": "text", "label": "Full Name", "event": "form:name"},
                {"type": "text", "label": "Email", "event": "form:email"},
                {"type": "secret", "label": "Password", "event": "form:password"},
                {"type": "select", "label": "Role", "event": "form:role", "options": [
                    {"label": "User", "value": "user"},
                    {"label": "Admin", "value": "admin"},
                ]},
                {"type": "toggle", "label": "Accept Terms", "event": "form:terms"},
                {"type": "button", "label": "Register", "event": "form:submit", "variant": "primary"},
            ]
        )
    ]
)

# Handle form
form_state = {}
while True:
    events = widget.get_events(clear=True)
    for e in events:
        if e["event_type"].startswith("form:"):
            field = e["event_type"].replace("form:", "")
            if field != "submit":
                form_state[field] = e["data"].get("value")
            else:
                # Validate
                if not form_state.get("name"):
                    widget.show_toast("Name is required", type="error")
                elif not form_state.get("terms"):
                    widget.show_toast("Please accept terms", type="error")
                else:
                    # Success
                    widget.set_content(
                        component_id="message",
                        html=f"Welcome, {form_state['name']}!"
                    )
                    widget.set_style(component_id="message", styles={"display": "block"})
    time.sleep(0.1)
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [deeleeramone](https://github.com/deeleeramone)
- **Source:** [deeleeramone/PyWry](https://github.com/deeleeramone/PyWry)
- **License:** Apache-2.0
- **Homepage:** https://deeleeramone.github.io/PyWry/

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-deeleeramone-pywry-forms-and-inputs
- Seller: https://agentstack.voostack.com/s/deeleeramone
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
