Skip to content
Development
Skill

/forms_and_inputs

Building interactive forms with validation and collecting user input.

From plugin
pywry
9918 skills1 agent2 commands1 MCP

Claude Code

$ npx -y skills add deeleeramone/PyWry --skill forms_and_inputs --agent claude-code

SKILL.md

forms_and_inputs.SKILL.md

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='<div id="results">Fill out the form and click Submit</div>',
    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 = """
<div class="form-group">
    <div id="name-error" class="error-message" style="display: none; color: var(--error-color);"></div>
</div>
"""

# 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: {
Read more
@deeleeramone
Ships withpywry

PyWry is a cross-platform app factory, rendering engine and UI toolkit for Python that produces native desktop, web, and notebook experiences from a single API.

9918 skills1 agent1 MCP
Get the whole plugin

Stars on GitHub

Momentum

stars in total
99
Star history of deeleeramone/PyWry on GitHub, a chart by star-history.com

Other skills on pywry.

pywry-orientation

pywry-orientation

When to reach for PyWry MCP tools (native webview rendering, Plotly, TradingView, AgGrid, chat) instead of writing…

@deeleeramone@deeleeramoneInstall Skill
chat_agent

chat_agent

How an agent operates inside a PyWry chat widget — reading user messages, attachments, @-context, tool-call result cards,…

@deeleeramone@deeleeramoneInstall Skill
component_reference

component_reference

**STOP. THIS IS THE AUTHORITATIVE REFERENCE FOR ALL COMPONENTS.** **YOU MUST USE THE EXACT EVENT SIGNATURES DOCUMENTED HERE.**…

@deeleeramone@deeleeramoneInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills11d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc13d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react2d252k
@facebook@facebookInstall Skill

Questions about forms_and_inputs.

Which coding agents can use forms_and_inputs?
forms_and_inputs installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.