Component library
Dev-only page (404s outside Development) for seeing this template's Tag Helpers live.
Source: src/Train.Web/TagHelpers.
Buttons
Built from this template's semantic tokens (app.css's @theme block —
bg-brand, text-heading, rounded-base, ...), not raw Tailwind
palette colors, so re-theming is "change the token value" rather than "grep every component".
<train-button>Default</train-button>
<train-button variant="secondary">Secondary</train-button>
<train-button variant="danger" size="sm">Small danger</train-button>
<train-button variant="primary" disabled>Disabled</train-button>
variant defaults to "primary"; size defaults to "md".
Form fields
Submit empty to see <train-field> report validation errors the same way a real page's form would.
<train-field asp-for="Input.Email" label="Email address" type="email" placeholder="you@example.com" />
<train-field asp-for="Input.Password" label="Password" type="password" help="At least 8 characters." />
<train-field asp-for="Input.Bio" label="Bio" type="textarea" placeholder="Tell us about yourself" />
<train-field asp-for="Input.FloatingEmail" label="Email address" type="email" variant="floating" />
<train-field asp-for="Input.Role" label="Role" type="select" items="Html.GetEnumSelectList<SomeEnum>()" />
<train-field asp-for="Input.AcceptTerms" type="checkbox" label="I accept the terms" />
type defaults to "text"; variant defaults to "stacked"
(label above the input) and is ignored for type="checkbox". items is required
for type="select".
Card
This block is itself a <train-card> — the wrapper you're reading this text inside of. It takes
arbitrary child content, with an optional title.
Tabs
Click a tab, or focus one and use Left/Right/Home/End — everything's server-rendered up front
(TagHelpers/TabsTagHelper.cs), wwwroot/js/tabs.js just toggles visibility.
Overview panel content. Each <train-tab> renders nothing itself — TabsTagHelper collects every child's label and content and builds the tablist/panel pair from that.
Details panel content.
Settings panel content.
<train-tabs>
<train-tab label="Overview">...</train-tab>
<train-tab label="Details">...</train-tab>
<train-tab label="Settings">...</train-tab>
</train-tabs>
The first <train-tab> starts active. <train-tab> is only valid directly inside <train-tabs>.
Banner
An announcement strip with an icon, message, optional CTA link, and a close button.
wwwroot/js/banner.js removes it from the DOM on close — dismissal isn't persisted, so it's
back on the next page load. Click the × to try it (reload the page to bring it back).
<train-banner cta-text="See changelog" cta-href="/changelog">
New: DataTable now supports infinite scroll for large result sets.
</train-banner>
cta-text/cta-href are optional — omit either to skip the CTA link.
dismissible defaults to true; set dismissible="false" to drop the
close button.
Image upload
Pick a file and it uploads immediately — wwwroot/js/image-upload.js posts it to
Admin/Uploads/Image.cshtml.cs, which stores it (locally in development, Azure Blob
Storage in production once AzureStorage:ConnectionString is configured) and returns its
URL. The bound field just ends up holding that URL, same as any other <train-field>.
<train-image-upload asp-for="Input.ImageUrl" label="Photo" />
JPEG, PNG, GIF, and WebP only, up to 5 MB. Rejected uploads leave the current value untouched.
No file chosen
<train-image-upload asp-for="Input.ImageUrl" label="Photo" variant="floating" />
Panel
A card with an Edit/Add/Delete icon toolbar (wwwroot/js/panel.js toggles which icons and which
half of each <train-panel-field> are visible — Save is a plain type="submit", so
saving is a normal postback like every other form here). Built for Admin/UtilityBar/Edit's Brand
tab first; the shell takes arbitrary body content, so later panels aren't limited to
label/value grids like this one.
<train-panel title="Brand" columns="3" editable="true">
<train-panel-field asp-for="Input.PanelTitle" label="Title" />
<train-panel-field asp-for="Input.PanelSubtitle" label="Subtitle" />
<train-panel-field asp-for="Input.PanelLogoUrl" label="Logo" type="image" />
</train-panel>
addable/deletable add their own icons next to Edit — this demo only sets
editable since a fixed Brand-shaped panel has nothing to add or delete.
Carousel
Prev/next buttons, dot indicators, and optional autoplay (paused on hover/focus) —
wwwroot/js/carousel.js just toggles visibility, everything's server-rendered up front
(TagHelpers/CarouselTagHelper.cs).
<train-carousel autoplay-interval="4000">
<train-carousel-slide>...</train-carousel-slide>
<train-carousel-slide>...</train-carousel-slide>
<train-carousel-slide>...</train-carousel-slide>
</train-carousel>
autoplay-interval (ms) is optional — omit it to disable autoplay. Prev/next and dots only
render when there's more than one slide.
DataTable
Two modes: Admin → Navigation is a small, fully-rendered table with client-side sort/search/filter. DataTable at scale is the same component in paged mode — 250,000 rows, server-side sort/search/filter, and infinite scroll with prefetch — for when a table has too many rows to ever render at once.