
Collect User Input
FreeBuild and validate forms in Blazor applications.
Free · Opens the source repo
What Collect User Input does
The Collect User Input skill provides a systematic approach to handling user input in Blazor applications. It enables developers to create forms, validate data, and respond to user interactions efficiently. This skill is particularly useful for building data-entry interfaces, such as forms for employee information, search boxes, and file uploads, ensuring that user inputs are captured accurately and validated appropriately.
This skill emphasizes the use of the EditForm component, which is essential for managing form submissions and validation. It supports both model-based and EditContext-based setups, allowing developers to choose the method that best fits their application needs. The skill also covers built-in input components like text fields, checkboxes, and dropdowns, making it easier to create interactive forms with minimal effort.
Validation is a key feature of this skill, utilizing data annotations and custom validators to ensure that user inputs meet specified criteria. Developers can leverage the DataAnnotationsValidator for standard validation or implement custom validation logic for more complex scenarios. The skill also provides guidance on handling form submissions, including how to manage valid and invalid states effectively.
Overall, this skill is designed for developers working with Blazor who need to implement user input forms that are both functional and user-friendly, streamlining the process of data collection and validation in their applications.
When to use it
Use this skill when you need to create interactive forms for data entry in Blazor applications, including scenarios requiring validation and submission handling.
When not to use it
Avoid this skill for project scaffolding or when working with prerendering issues, as it is focused solely on form handling and validation.
What you can build with it
Employee Data Entry Form
Use this skill to create a form for entering employee details, ensuring all fields are validated before submission.
Search and Filter Panel
Implement a search box or filter panel that reacts to user input, allowing for dynamic data retrieval.
File Uploads in Blazor
Utilize the file upload component to allow users to submit files as part of their input, with validation for file types.
How to install Collect User Input
View source1. Install with the skills CLI
npx skills add dotnet/skills/collect-user-input --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by dotnetCollect User Input
Step 1 — Read the Project's AGENTS.md
Check AGENTS.md for Interactivity Mode and Interactivity Scope. This determines which form patterns apply:
| Mode | Form mechanism |
|---|---|
| None (Static SSR) | EditForm with FormName + [SupplyParameterFromForm]. No @bind, no @onchange. |
| Server | EditForm with @bind-Value. Full interactivity — real-time validation, dynamic UI. |
| WebAssembly | Same as Server, but validators needing server data must call APIs. |
| Auto | Same as WebAssembly — code must work in both browser and server. |
| Scope | Impact |
|---|---|
| Global | All forms are interactive. FormName only needed when explicitly opting a page to static SSR. |
| Per-page | Forms in static pages use FormName + [SupplyParameterFromForm]. Forms in @rendermode pages use @bind-Value. |
EditForm Setup
EditForm requires either Model or EditContext — never both.
Model-based (default)
<EditForm Model="Employee" OnValidSubmit="HandleSubmit" FormName="employee">
<DataAnnotationsValidator />
<ValidationSummary />
<label>
Name: <InputText @bind-Value="Employee!.Name" />
<ValidationMessage For="() => Employee!.Name" />
</label>
<button type="submit">Save</button>
</EditForm>
@code {
[SupplyParameterFromForm]
private EmployeeModel? Employee { get; set; }
protected override void OnInitialized() => Employee ??= new();
private async Task HandleSubmit()
{
// Save Employee
}
}
This single pattern works in both SSR and interactive modes:
- In SSR:
FormNameidentifies the form,[SupplyParameterFromForm]binds POST data,??=initializes on GET. - In interactive:
@bind-Valueprovides two-way binding,[SupplyParameterFromForm]is ignored,FormNameis harmless.
EditContext-based (advanced)
Use when you need programmatic field tracking, dynamic validation rules, or manual EditContext.Validate() calls:
private EditContext? editContext;
private EmployeeModel model = new();
protected override void OnInitialized()
{
editContext = new EditContext(model);
}
<EditForm EditContext="editContext" OnValidSubmit="HandleSubmit" FormName="employee">
Submit Handlers
| Handler | Fires when | Use when |
|---|---|---|
OnValidSubmit | Validation passes | Standard forms with DataAnnotationsValidator |
OnInvalidSubmit | Validation fails | Need custom handling for invalid state |
OnSubmit | Always — validation is manual | Using EditContext.Validate() yourself |
OnSubmit cannot combine with OnValidSubmit/OnInvalidSubmit.
Built-in Input Components
| Component | Binds to | Notes |
|---|---|---|
InputText | string | Renders <input type="text"> |
InputTextArea | string | Renders <textarea> |
InputNumber<T> | int, double, decimal | Renders <input type="number"> |
InputDate<T> | DateTime, DateOnly, DateTimeOffset | Renders <input type="date"> |
InputCheckbox | bool | Renders <input type="checkbox"> |
InputSelect<T> | string, enums, numeric types | Renders <select> |
InputRadioGroup<T> | string, enums, numeric types | Wraps InputRadio<T> children |
InputFile | IBrowserFile | File upload — interactive modes only |
All input components use @bind-Value for binding. Always wrap text in a <label> or use id/for attributes for accessibility.
InputSelect with enum values
<InputSelect @bind-Value="Model!.Status">
<option value="">-- Select --</option>
@foreach (var value in Enum.GetValues<OrderStatus>())
{
<option value="@value">@value</option>
}
</InputSelect>
InputRadioGroup
<InputRadioGroup @bind-Value="Model!.Priority">
@foreach (var p in Enum.GetValues<Priority>())
{
<label>
<InputRadio Value="p" /> @p
</label>
}
</InputRadioGroup>
Validation
Data annotations
Define validation rules on the model:
public class EmployeeModel
{
[Required, StringLength(100)]
public string? Name { get; set; }
[Required, EmailAddress]
public string? Email { get; set; }
[Range(18, 99)]
public int Age { get; set; }
[Required]
public string? Department { get; set; }
}
Add <DataAnnotationsValidator /> inside EditForm — without it, annotation attributes are silently ignored.
Display errors with:
<ValidationSummary />— all errors in a list<ValidationMessage For="() => Model!.FieldName" />— per-field inline errors
Custom validator component
For server-round-trip validation (uniqueness checks, business rules):
public class CustomValidator : ComponentBase
{
[CascadingParameter]
private EditContext? EditContext { get; set; }
private ValidationMessageStore? messageStore;
protected override void OnInitialized()
{
messageStore = new ValidationMessageStore(EditContext!);
EditContext!.OnValidationRequested += (s, e) => messageStore.Clear();
EditContext!.OnFieldChanged += (s, e) => messageStore.Clear(e.FieldIdentifier);
}
public void DisplayErrors(Dictionary<string, List<string>> errors)
{
foreach (var (field, messages) in errors)
{
foreach (var message in messages)
{
messageStore!.Add(EditContext!.Field(field), message);
}
}
EditContext!.NotifyValidationStateChanged();
}
public void ClearErrors()
{
messageStore?.Clear();
EditContext?.NotifyValidationStateChanged();
}
}
Usage in a form:
<EditForm Model="Model" OnValidSubmit="HandleSubmit" FormName="register">
<DataAnnotationsValidator />
<CustomValidator @ref="customValidator" />
<ValidationSummary />
@* inputs *@
</EditForm>
@code {
private CustomValidator? customValidator;
private async Task HandleSubmit()
{
var errors = await RegistrationService.ValidateAsync(Model!);
if (errors.Count > 0)
{
customValidator!.DisplayErrors(errors);
return;
}
// proceed
}
}
React to Input Changes (Interactive Only)
@bind:after
Run logic after a bound value changes:
<InputText @bind-Value="Model!.ZipCode" @bind:after="OnZipCodeChanged" />
@code {
private async Task OnZipCodeChanged()
{
// Fetch city/state based on new zip code
var location = await LocationService.LookupAsync(Model!.ZipCode);
Model.City = location?.City;
Model.State = location?.State;
}
}
@oninput for real-time filtering
<input type="text" @oninput="OnSearchInput" placeholder="Search..." />
@code {
private string searchTerm = "";
private List<Item> filteredItems = new();
private void OnSearchInput(ChangeEventArgs e)
{
searchTerm = e.Value?.ToString() ?? "";
filteredItems = allItems.Where(i =>
i.Name.Contains(searchTerm, StringComparison.OrdinalIgnoreCase)).ToList();
}
}
SSR-Specific Patterns
These apply when the form renders in Static SSR (mode = None, or per-page without @rendermode).
SupplyParameterFromForm
Binds POST data to a property on form submission:
[SupplyParameterFromForm]
private ContactModel? Contact { get; set; }
protected override void OnInitialized() => Contact ??= new();
Critical: The ??= in OnInitialized is required. On GET the property is null — ??= creates the model. On POST the framework populates it — ??= preserves the posted values.
FormName — multiple forms on one page
Each form needs a unique FormName:
<EditForm Model="Search" OnSubmit="DoSearch" FormName="search">...</EditForm>
<EditForm Model="Contact" OnValidSubmit="SaveContact" FormName="contact">...</EditForm>
Match [SupplyParameterFromForm] to its form:
[SupplyParameterFromForm(FormName = "search")]
private SearchModel? Search { get; set; }
[SupplyParameterFromForm(FormName = "contact")]
private ContactModel? Contact { get; set; }
Enhanced navigation for forms
Add Enhance for SPA-like form submissions without full page reload:
<EditForm Model="Model" OnValidSubmit="Save" FormName="quick" Enhance>
Enhanced forms submit via fetch, patch the DOM, and preserve scroll position. The page stays interactive-feeling even in SSR.
Plain HTML forms
When using raw <form> instead of EditForm in SSR, add the antiforgery token manually:
<form method="post" @onsubmit="Submit" @formname="raw-form">
<AntiforgeryToken />
<input name="Model.Name" value="@Model?.Name" />
<button type="submit">Send</button>
</form>
EditForm includes the antiforgery token automatically.
File Upload
InputFile works in interactive modes only — not in Static SSR.
<InputFile OnChange="OnFileSelected" accept=".pdf,.jpg,.png" />
@code {
private IBrowserFile? selectedFile;
private async Task OnFileSelected(InputFileChangeEventArgs e)
{
selectedFile = e.File;
// Read stream with size limit
await using var stream = selectedFile.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
// Process stream — save to disk, upload to storage, etc.
}
}
Stream size limits:
- Server: Default ~30 KB SignalR message size. Call
OpenReadStream(maxAllowedSize)to increase. Large files stream over the circuit. - WebAssembly: File is read in the browser. No SignalR limit, but memory constrained.
For multiple files:
<InputFile OnChange="OnFilesSelected" multiple />
@code {
private async Task OnFilesSelected(InputFileChangeEventArgs e)
{
foreach (var file in e.GetMultipleFiles(maxAllowedFiles: 10))
{
await using var stream = file.OpenReadStream(maxAllowedSize: 10 * 1024 * 1024);
// Process each file
}
}
}
Prevent Double Submission
Disable the submit button while processing:
<button type="submit" disabled="@isSubmitting">
@(isSubmitting ? "Saving..." : "Save")
</button>
@code {
private bool isSubmitting;
private async Task HandleSubmit()
{
isSubmitting = true;
try
{
await SaveService.SaveAsync(Model!);
}
finally
{
isSubmitting = false;
}
}
}
Custom Validation CSS
Replace the default valid/invalid CSS classes:
public class BootstrapFieldCssClassProvider : FieldCssClassProvider
{
public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
{
var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
return editContext.IsModified(fieldIdentifier)
? (isValid ? "is-valid" : "is-invalid")
: "";
}
}
Apply to the form:
protected override void OnInitialized()
{
editContext = new EditContext(model);
editContext.SetFieldCssClassProvider(new BootstrapFieldCssClassProvider());
}
Don'ts
- Don't use
@bindor@oninputin Static SSR forms — they require interactivity. Use[SupplyParameterFromForm]andFormName. - Don't forget
Model ??= new()inOnInitialized— the model is null on GET, populated on POST. - Don't use
OnSubmittogether withOnValidSubmit/OnInvalidSubmit— they're mutually exclusive. - Don't omit
<DataAnnotationsValidator />— validation attributes are silently ignored without it. - Don't omit
FormNamein SSR when a page has multiple forms — both forms will fire on any submission. - Don't use
InputFilein Static SSR — it requires an interactive render mode. - Don't use both
ModelandEditContexton anEditForm— pick one. - Don't forget
<AntiforgeryToken />in plain<form>elements — the server rejects the POST without it.
Frequently asked questions about Collect User Input
Similar skills
Playwright Component Testing
Test React and Vue components in isolation with Playwright.
Fluent UI Blazor
Integrate Fluent UI components in Blazor applications effortlessly.
Build MCP App
Create interactive UI widgets for MCP servers.
Web Design Reviewer
Identify and fix design issues in websites efficiently.
Markstream Install
Seamlessly integrate Markstream for Markdown rendering.
GSAP & Framer Scroll Animation
Create advanced scroll animations effortlessly.
