Install
$ agentstack add skill-deeleeramone-pywry-forms-and-inputs ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
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
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
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
# 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
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
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
# 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
{"type": "text",
"label": "Username",
"event": "username",
"placeholder": "Enter username",
"value": "", # Default value
}
# Event data: {"value": "entered_text"}
Number Input
{"type": "number",
"label": "Quantity",
"event": "quantity",
"min": 1,
"max": 100,
"step": 1,
"value": 10,
}
# Event data: {"value": 10} # Always a number
Select
{"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
{"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
{"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
{"type": "toggle", # or "checkbox"
"label": "Enable notifications",
"event": "notifications",
"value": True, # Default state
}
# Event data: {"value": true} # Boolean
Slider
{"type": "slider",
"label": "Volume",
"event": "volume",
"min": 0,
"max": 100,
"step": 5,
"value": 50,
}
# Event data: {"value": 50}
Range (Two Values)
{"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
{"type": "date",
"label": "Start Date",
"event": "start_date",
"value": "2024-01-01", # ISO format
}
# Event data: {"value": "2024-01-15"}
Search (with debounce)
{"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)
{"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
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
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
- Source: deeleeramone/PyWry
- License: Apache-2.0
- Homepage: https://deeleeramone.github.io/PyWry/
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.