Field
Easily create form fields with labels, help text and input controls
<sa-field-group>
<sa-field-set>
<sa-field-legend>Payment Method</sa-field-legend>
<sa-field-description>
All transactions are secure and encrypted
</sa-field-description>
<sa-field-group>
<sa-field>
<sa-field-label for="card-name">
Name on Card
</sa-field-label>
<sa-input
id="card-name"
placeholder="Ibn Battuta"
/>
</sa-field>
<sa-field>
<sa-field-label for="card-number">
Card Number
</sa-field-label>
<sa-input
id="card-number"
placeholder="1234 5678 9012 3456"
/>
<sa-field-description>
Enter your 16-digit card number
</sa-field-description>
</sa-field>
<div class="grid grid-cols-3 gap-4">
<sa-field>
<sa-field-label for="exp-month">
Month
</sa-field-label>
<sa-select id="exp-month">
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
<option value="04">04</option>
<option value="05">05</option>
<option value="06">06</option>
<option value="07">07</option>
<option value="08">08</option>
<option value="09">09</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</sa-select>
</sa-field>
<sa-field>
<sa-field-label for="exp-year">
Year
</sa-field-label>
<sa-select id="exp-year">
<option value="2024">2024</option>
<option value="2025">2025</option>
<option value="2026">2026</option>
<option value="2027">2027</option>
<option value="2028">2028</option>
<option value="2029">2029</option>
</sa-select>
</sa-field>
<sa-field>
<sa-field-label for="cvv">CVV</sa-field-label>
<sa-input id="cvv" placeholder="123"/>
</sa-field>
</div>
</sa-field-group>
</sa-field-set>
<sa-field-separator/>
<sa-field-set>
<sa-field-legend>Billing Address</sa-field-legend>
<sa-field-description>
The billing address associated with your payment method
</sa-field-description>
<sa-field-group>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input
type="checkbox"
id="same-as-shipping"
/>
<sa-field-label
for="same-as-shipping"
class="font-normal"
>
Same as shipping address
</sa-field-label>
</sa-field>
</sa-field-group>
</sa-field-set>
<sa-field-set>
<sa-field-group>
<sa-field>
<sa-field-label for="optional-comments">
Comments
</sa-field-label>
<sa-textarea
id="optional-comments"
placeholder="Add any additional comments"
class="resize-none"
/>
</sa-field>
</sa-field-group>
</sa-field-set>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-button type="button">Submit</sa-button>
<sa-button variant="ButtonVariant.Outline" type="button">
Cancel
</sa-button>
</sa-field>
</sa-field-group>Usage
The <sa-field> family of Tag Helpers are designed for composing accessible forms. <sa-field> is the core wrapper for a single field.
<sa-field>
<sa-field-label for="input-id">Label</sa-field-label>
<!-- We use sa-input below, but can be any StellarAdmin.UI input such as sa-select, sa-textarea, etc. -->
<sa-input id="input-id" />
<sa-field-description>Optional helper text</sa-field-description>
<sa-field-error>Validation message</sa-field-error>
</sa-field><sa-field-content> is a flex column that groups label and description. This component is useful when you use a field in horizontal orientation
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input id="input-id" type="checkbox"/>
<sa-field-content>
<sa-field-label for="input-id">
Sync map with the itinerary
</sa-field-label>
<sa-field-description>
Places from the itinerary are shown on the map automatically. Changes you make here are visible to all
travellers on the booking.
</sa-field-description>
</sa-field-content>
</sa-field>Wrap related fields with <sa-field-group>, and use <sa-field-set> with <sa-field-legend> for semantic grouping.
<sa-field-set>
<sa-field-legend>Payment Method</sa-field-legend>
<sa-field-description>
All transactions are secure and encrypted
</sa-field-description>
<sa-field-group>
<sa-field>
<sa-field-label for="card-name">
Name on Card
</sa-field-label>
<sa-input
id="card-name"
placeholder="Ibn Battuta"
/>
</sa-field>
<sa-field>
<sa-field-label for="card-number">
Card Number
</sa-field-label>
<sa-input
id="card-number"
placeholder="1234 5678 9012 3456"
/>
<sa-field-description>
Enter your 16-digit card number
</sa-field-description>
</sa-field>
</sa-field-group>
</sa-field-set>Explicit and implicit fields
The usage examples demonstrated so far require a lot of boilerplate when creating fields by specifying <sa-field>, <sa-field-label>, <sa-field-description>, etc. To improve the developer experience, all the StellarAdmin input controls such as <sa-input>, <sa-select>, <sa-textarea>, etc. can implicitly create fields using one of the following two techniques.
-
When using model binding (with the
asp-forattribute), StellarAdmin will implicitly wrap the input inside a<sa-field>with the corresponding<sa-field-label>and<sa-field-description>inferred from data annotations. If your model contains errors related to the bound property, a<sa-field-error>component with the error message will also be added.<sa-input asp-for="Model.Name"/> -
If you are not using model binding, you can also opt in to implicit fields by specifying any combination of the
label,description, anderrorattributes. When doing this, your input will be wrapped inside a<sa-field>with the corresponding<sa-field-label>,<sa-field-description>, and<sa-field-error>automatically added.<sa-input label="First Name" description="First name as displayed on card" error="Enter the name exactly as it appears on the card"/>
In both cases mentioned above, you can opt out of implicit fields by setting the render-field attribute to false. In the example below, StellarAdmin will not create an implicit field, even though we're using model binding.
<sa-input render-field="false" asp-for="Model.Name"/>Most of the examples in this document demonstrate both explicit and implicit mode. Notice how compact the implicit examples are compared to their explicit counterparts.
Examples
Implicit fields
Whenever you use any of the various field input Tag Helpers (such as <sa-input>, <sa-textarea>, <sa-select> etc.) along with data binding, StellarAdmin will implicitly create a field with a label, description, and placeholder for you. You can see this demonstrated in the example below which implements the same form as above, but using data binding.
@{
var months = Enumerable.Range(1, 12).Select(i => $"{i:00}").Select(i => new SelectListItem(i, i));
var years = Enumerable.Range(2024, 6).Select(i => new SelectListItem(i.ToString(), i.ToString()));
}
<sa-field-group>
<sa-field-set>
<sa-field-legend>Payment Method</sa-field-legend>
<sa-field-description>
All transactions are secure and encrypted
</sa-field-description>
<sa-field-group>
<sa-input asp-for="Name"/>
<sa-input asp-for="CardNumber"/>
<div class="grid grid-cols-3 gap-4">
<sa-select asp-for="Month" asp-items="@months" />
<sa-select asp-for="Year" asp-items="@years" />
<sa-input asp-for="Cvv"/>
</div>
</sa-field-group>
</sa-field-set>
<sa-field-separator/>
<sa-field-set>
<sa-field-legend>Billing Address</sa-field-legend>
<sa-field-description>
The billing address associated with your payment method
</sa-field-description>
<sa-field-group>
<sa-input asp-for="IsBillingAddressSame" />
</sa-field-group>
</sa-field-set>
<sa-field-set>
<sa-field-group>
<sa-textarea asp-for="Comments"/>
</sa-field-group>
</sa-field-set>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-button type="button">Submit</sa-button>
<sa-button variant="ButtonVariant.Outline" type="button">
Cancel
</sa-button>
</sa-field>
</sa-field-group>Input
Text and password inputs inside fields, each with a description below the control.
<sa-field-set>
<sa-field-group>
<sa-field>
<sa-field-label for="email">Email</sa-field-label>
<sa-input id="email" type="text" placeholder="e.g. ibn.battuta@rihlah.travel"/>
<sa-field-description>Your email address</sa-field-description>
</sa-field>
<sa-field>
<sa-field-label for="password">Password</sa-field-label>
<sa-input id="password" type="password" placeholder="••••••••"/>
<sa-field-description>
Must be at least 8 characters long.
</sa-field-description>
</sa-field>
</sa-field-group>
</sa-field-set>Textarea
A field with a textarea and a description.
<sa-field>
<sa-field-label for="feedback">Feedback</sa-field-label>
<sa-textarea
id="feedback"
placeholder="Your feedback helps us improve..."
rows="4"/>
<sa-field-description>
Share your thoughts about our service.
</sa-field-description>
</sa-field>Select
A select inside a field. The description sits below the control.
<sa-field>
<sa-field-label>Travel style</sa-field-label>
<sa-select>
<option value="city">City breaks</option>
<option value="beach" selected>Beach holidays</option>
<option value="adventure">Adventure</option>
<option value="cruise">Cruises</option>
<option value="safari">Safari</option>
<option value="ski">Ski</option>
<option value="cultural">Cultural tours</option>
<option value="family">Family</option>
</sa-select>
<sa-field-description>
Select the type of trip you take most often.
</sa-field-description>
</sa-field>Checkbox
Horizontal fields place the checkbox before its label. The field set groups related options, and a separator divides the sections.
<sa-field-group>
<sa-field-set>
<sa-field-legend variant="FieldLegendVariant.Label">
Show on the destination map
</sa-field-legend>
<sa-field-description>
Select the places you want to see on the destination map.
</sa-field-description>
<sa-field-group class="gap-3">
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="map-pref-landmarks"/>
<sa-field-label
for="map-pref-landmarks"
class="font-normal">
Landmarks and attractions
</sa-field-label>
</sa-field>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="map-pref-restaurants"/>
<sa-field-label
for="map-pref-restaurants"
class="font-normal">
Restaurants and bars
</sa-field-label>
</sa-field>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="map-pref-transit"/>
<sa-field-label
for="map-pref-transit"
class="font-normal">
Transit stations
</sa-field-label>
</sa-field>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="map-pref-guides"/>
<sa-field-label
for="map-pref-guides"
class="font-normal">
Local tour guides
</sa-field-label>
</sa-field>
</sa-field-group>
</sa-field-set>
<sa-field-separator/>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="map-pref-sync-itinerary" checked="checked"/>
<sa-field-content>
<sa-field-label for="map-pref-sync-itinerary">
Sync map with the itinerary
</sa-field-label>
<sa-field-description>
Places from the itinerary are shown on the map automatically. Changes you make here are visible to all
travellers on the booking.
</sa-field-description>
</sa-field-content>
</sa-field>
</sa-field-group>Radio
Radios use horizontal fields inside a field set and share a name so only one can be selected.
<sa-field-set>
<sa-field-legend variant="FieldLegendVariant.Label">Travel insurance</sa-field-legend>
<sa-field-description>
Annual cover works out cheaper if you take more than two trips a year.
</sa-field-description>
<sa-field-group data-slot="radio-group">
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="radio" name="cover" value="single" id="cover-single"/>
<sa-field-label for="cover-single" class="font-normal">
Single trip ($29.99)
</sa-field-label>
</sa-field>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="radio" name="cover" value="annual" id="cover-annual" checked/>
<sa-field-label for="cover-annual" class="font-normal">
Annual multi-trip ($89.99/year)
</sa-field-label>
</sa-field>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="radio" name="cover" value="family" id="cover-family"/>
<sa-field-label for="cover-family" class="font-normal">
Annual family ($149.99/year)
</sa-field-label>
</sa-field>
</sa-field-group>
</sa-field-set>Fieldset
Use <sa-field-set> with an <sa-field-legend> to group related fields under a heading, and lay them out with your own grid classes.
<sa-field-set>
<sa-field-legend>Billing address</sa-field-legend>
<sa-field-description>
We need your billing address to issue the invoice for your booking.
</sa-field-description>
<sa-field-group>
<sa-field>
<sa-field-label for="street">Street Address</sa-field-label>
<sa-input id="street" type="text" placeholder="12 Rue de la Kasbah"/>
</sa-field>
<div class="grid grid-cols-2 gap-4">
<sa-field>
<sa-field-label for="city">City</sa-field-label>
<sa-input id="city" type="text" placeholder="Marrakech"/>
</sa-field>
<sa-field>
<sa-field-label for="postal-code">Postal Code</sa-field-label>
<sa-input id="postal-code" type="text" placeholder="40000"/>
</sa-field>
</div>
</sa-field-group>
</sa-field-set>Field group
Stack <sa-field> components with <sa-field-group>. Add <sa-field-separator> to divide them. When grouping checkboxes or radio buttons, you can add data-slot="checkbox-group" or data-slot="radio-group" to tighten up spacing between the individual inputs.
<sa-field-group>
<sa-field-set>
<sa-field-label>Booking updates</sa-field-label>
<sa-field-description>
Get notified when a booking you manage is confirmed, changed, or
cancelled.
</sa-field-description>
<sa-field-group data-slot="checkbox-group">
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="push-bookings" checked disabled/>
<sa-field-label for="push-bookings" class="font-normal">
Push notifications
</sa-field-label>
</sa-field>
</sa-field-group>
</sa-field-set>
<sa-field-separator/>
<sa-field-set>
<sa-field-label>Flight alerts</sa-field-label>
<sa-field-description>
Get notified about gate changes and delays for upcoming flights. <a href="#">Manage flights</a>
</sa-field-description>
<sa-field-group data-slot="checkbox-group">
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="push-flights"/>
<sa-field-label for="push-flights" class="font-normal">
Push notifications
</sa-field-label>
</sa-field>
<sa-field orientation="FieldOrientation.Horizontal">
<sa-input type="checkbox" id="email-flights"/>
<sa-field-label for="email-flights" class="font-normal">
Email notifications
</sa-field-label>
</sa-field>
</sa-field-group>
</sa-field-set>
</sa-field-group>Accessibility
<sa-field-label> renders a real <label>. With asp-for, or with the label attribute on an input, the label's for attribute is wired to the input's id for you. When you write the label yourself without asp-for, set for to the input's id. <sa-field-set> and <sa-field-legend> render a native <fieldset> and <legend>, which is the right way to name a group of related inputs such as radio buttons.
<sa-field-error> renders with role="alert". The description and error are not linked to the input automatically. If you want them announced together with the input, give them an id and reference it from aria-describedby on the input. Set aria-invalid="true" on an input that is not model bound to expose its error state.
Part classes
Use the input's class-names attribute to style individual parts of an implicit field. Each input has its own options type in the StellarAdmin.TagHelpers namespace, such as InputClassNames for <sa-input>. These types inherit the shared field parts from FieldClassNames.
The example gives the field wrapper a tinted background, outlines the input, changes the label to uppercase, and italicizes the help text. It shows both an implicit field and an explicitly composed field using the same classes.
@{
var inputClasses = new InputClassNames
{
Root = "account-field",
Control = "account-input",
Label = "account-label",
Description = "account-help"
};
}
<sa-field-group>
<sa-input asp-for="Email" description="Used for account notifications."
class-names="@inputClasses" />
<sa-field class="@inputClasses.Root">
<sa-field-label asp-for="Password" class="@inputClasses.Label" />
<sa-input asp-for="Password" render-field="false"
class-names="@inputClasses" />
<sa-field-description class="@inputClasses.Description">
Use a password you have not used elsewhere.
</sa-field-description>
</sa-field>
</sa-field-group>All properties below are optional strings containing space-separated CSS classes. Classes are added to the component's existing classes.
| Property | Target | When applied |
|---|---|---|
Root | The generated <sa-field> wrapper. | When the input renders an implicit field. |
Label | The generated <sa-field-label>. | When a label is rendered. |
Description | The generated <sa-field-description> help text. | When a description is rendered. |
Error | The generated <sa-field-error> validation message. | When a validation message element is rendered. |
Content | The generated <sa-field-content> container around the label and help text. | When the implicit field uses horizontal orientation. |
Input-specific properties target the control itself. For example, InputClassNames.Control styles the native <input> in this example.
With render-field="false", the shared field parts have no generated elements to style; input-specific parts still apply. For explicitly composed fields, put class directly on <sa-field>, <sa-field-label>, and the other elements you write, as shown above. The input's class-names options do not style parent or sibling elements.
API Reference
<sa-field>
Renders a <div> element that wraps a single form field.
Prop
Type
<sa-field-label>
Renders a <label> element.
Prop
Type
<sa-field-description>
Renders a <p> element with help text for a field, forwarding any global attributes.
Prop
Type
<sa-field-error>
Renders a <div> element with role="alert" that displays a validation message, forwarding any global attributes. It is hidden unless a validation error is present.
Prop
Type
<sa-field-title>
Renders a <div> element for a non-<label> field title, forwarding any global attributes.
<sa-field-content>
Renders a <div> element that groups the label and description of a horizontal field, forwarding any global attributes.
<sa-field-group>
Renders a <div> element that stacks related fields, forwarding any global attributes.
<sa-field-separator>
Renders a <div> element containing a horizontal separator, with any child content rendered as text on top of the line.
<sa-field-set>
Renders a <fieldset> element.
<sa-field-legend>
Renders a <legend> element for a <sa-field-set>.
Prop
Type