Components
Input
A line of text, of any input type the browser has, bound to the model or held by the page. Everything inside the box (an icon, a shortcut, the clear, copy and reveal buttons) is a step, and a group joins a prefix, a suffix or a button to it.
Drawn and behaving as Flux UI's input does, measured against its live docs.
Input
In a field of its own making: a label, a description and the error, around the input.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputExample : Component
{
private string Username { get; set; } = "";
protected override Component? Render() =>
Ui.Field.Class("max-w-sm")[
Ui.Label["Username"],
Ui.Description["This will be publicly displayed."],
Ui.Input.Bind(() => Username),
Ui.Error
];
}Shorthand
Label and Description on the input draw the same field, with its error.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputShorthandExample : Component
{
protected override Component? Render() =>
Ui.Input.Of<string>().Label("Username").Description("This will be publicly displayed.").Class("max-w-sm");
}Class targeting
The input is a wrapper around an <input>: Class goes on the wrapper, InputClass on the <input> itself.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputClassTargetingExample : Component
{
protected override Component? Render() =>
Ui.Input.Value("BA7K-7QZ5").Class("max-w-xs").InputClass("font-mono");
}Types
The browser's input types, email, password, date and the rest, from Type.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputTypesExample : Component
{
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-6")[
Ui.Input.Of<string>().Type(InputType.Email).Label("Email"),
Ui.Input.Of<string>().Type(InputType.Password).Label("Password"),
Ui.Input.Of<string>().Type(InputType.Date).Max("2999-12-31").Label("Date")
];
}File
A file input with a "Choose files" button and the chosen name; OnFiles hands the page what was chosen.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputFileExample : Component
{
private int _chosen;
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-6")[
Ui.Input.Of<string>().Type(InputType.File).Label("Logo").OnFiles(files => { _chosen = files.Count; }),
Ui.Input.Of<string>().Type(InputType.File).Label("Attachments").Multiple().OnFiles(files => { _chosen = files.Count; }),
Ui.Text[$"Files chosen last: {_chosen}."]
];
}Files chosen last: 0.
Smaller
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputSmallerExample : Component
{
protected override Component? Render() =>
Ui.Input.Of<string>().Sm.Placeholder("Filter by...").Class("max-w-xs");
}Disabled
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputDisabledExample : Component
{
protected override Component? Render() =>
Ui.Input.Of<string>().Disabled().Label("Email").Class("max-w-sm");
}Readonly
For a value that may be read and copied but not changed, here drawn filled.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputReadonlyExample : Component
{
protected override Component? Render() =>
Ui.Input.Value("BA7K7QZ511S8Z2K").ReadOnly().Filled.Class("max-w-sm");
}Invalid
Error styling the form did not ask for. A bound input is marked invalid on its own.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputInvalidExample : Component
{
protected override Component? Render() =>
Ui.Input.Of<string>().Invalid().Class("max-w-sm");
}Input masking
Each keystroke is held to the pattern: 9 is a digit, a a letter, * either.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputMaskExample : Component
{
protected override Component? Render() =>
Ui.Input.Value("7161234567").Mask("(999) 999-9999").Attributes(("aria-label", "Phone")).Class("max-w-sm");
}Icons
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputIconsExample : Component
{
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-4")[
Ui.Input.Of<string>().Icon(Ui.IconName.MagnifyingGlass).Placeholder("Search orders"),
Ui.Input.Of<string>().IconTrailing(Ui.IconName.CreditCard).Placeholder("4444-4444-4444-4444"),
Ui.Input.Of<string>().IconTrailing(Ui.IconName.Loading).Placeholder("Search transactions")
];
}Icon buttons
IconTrailing also takes content of your own, such as a button.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputIconButtonsExample : Component
{
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-4")[
Ui.Input.Of<string>().Placeholder("Search orders").IconTrailing(
Ui.Button.Sm.Subtle.Icon(Ui.IconName.XMark).Class("-mr-1").Aria("label", "Clear")),
Ui.Input.Value("password").Type(InputType.Password).IconTrailing(
Ui.Button.Sm.Subtle.Icon(Ui.IconName.Eye).Class("-mr-1").Aria("label", "Show password"))
];
}Clearable, copyable, and viewable inputs
Three buttons the input draws itself: one that empties it, one that reveals a password, one that copies the value.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputClearableExample : Component
{
private string _search = "Jack Skellington";
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-4")[
Ui.Input.Value(_search).Placeholder("Search orders").Clearable()
.OnInput(text => { _search = text; }).OnChange(text => { _search = text; }),
Ui.Input.Value("password").Type(InputType.Password).Viewable(),
Ui.Input.Value("FLUX-1234-5678-ABCD-EFGH").Icon(Ui.IconName.Key).ReadOnly().Copyable(),
Ui.Text.Data("testid", "ui-input-clearable-state")[$"Searching for \"{_search}\"."]
];
}Searching for "Jack Skellington".
Keyboard hint
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputKbdExample : Component
{
protected override Component? Render() =>
Ui.Input.Of<string>().Kbd("⌘K").Icon(Ui.IconName.MagnifyingGlass).Placeholder("Search...").Class("max-w-sm");
}As a button
A <button> drawn as the input, for a search that opens a palette.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputAsButtonExample : Component
{
private int _pressed;
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-4")[
Ui.Input.Of<string>().As(Ui.InputAs.Button).Placeholder("Search...").Icon(Ui.IconName.MagnifyingGlass).Kbd("⌘K")
.OnClick(() => { _pressed++; }),
Ui.Text[$"Pressed {_pressed} time{(_pressed == 1 ? "" : "s")}."]
];
}Pressed 0 times.
With buttons
An input group joins its neighbours into one outline: the two ends keep their corners and a border is drawn once between them.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputWithButtonsExample : Component
{
private string? _currency = "USD";
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-4")[
Ui.InputGroup[
Ui.Input.Of<string>().Placeholder("Post title"),
Ui.Button.Icon(Ui.IconName.Plus)["New post"]
],
Ui.InputGroup[
Ui.Select.Value(_currency).OnChange(currency => { _currency = currency; }).Class("max-w-fit")[
Ui.SelectOption["USD"],
Ui.SelectOption["EUR"],
Ui.SelectOption["GBP"]
],
Ui.Input.Of<string>().Placeholder("$99.99")
]
];
}Text prefixes and suffixes
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputAffixesExample : Component
{
protected override Component? Render() =>
Div.Class("grid max-w-sm gap-4")[
Ui.InputGroup[
Ui.InputGroupPrefix["https://"],
Ui.Input.Of<string>().Placeholder("example.com")
],
Ui.InputGroup[
Ui.Input.Of<string>().Placeholder("chunky-spaceship"),
Ui.InputGroupSuffix[".brand.com"]
]
];
}Input group labels
For a label, the group goes in a field, which reaches the input inside it.
namespace Rask.Site.Features.UiDocs.Examples;
public sealed partial class InputGroupLabelExample : Component
{
private string Website { get; set; } = "";
protected override Component? Render() =>
Ui.Field.Class("max-w-sm")[
Ui.Label["Website"],
Ui.InputGroup[
Ui.InputGroupPrefix["https://"],
Ui.Input.Bind(() => Website).Placeholder("example.com")
],
Ui.Error
];
}Behaviour
When the value is written
Bind(() => m.Email) or Value(email).OnChange(…) stands where Flux has wire:model. A bound input is written with the reader's next action and travels with it, as wire:model does; .Live() writes as it is typed, after a 150 ms pause (wire:model.live), .Debounce(300.Milliseconds) after a pause of your own, and .Blur() when the reader leaves the field (wire:model.blur). OnInput hears every keystroke.
The type comes from the model: Ui.Input.Bind(() => m.Seats) over an int is a number field with nothing said at the call site. A field with nothing in it yet opens on its type alone: Ui.Input.Of<string>().Label("Search").
No script of the kit's
What Flux does in Alpine is the runtime's, by attribute. The clear button (data-rask-clear) empties the field in the click and leaves the focus in it, and CSS hides it while the field is empty. The copy button (data-rask-copy) writes the clipboard in the click and shows a tick for two seconds. Mask holds each keystroke to its pattern (data-rask-mask), and the value drawn and committed is shaped the same way. The reveal button is a handler. A file input is a <label> around the real input.
Attributes(("aria-label", "Search keys")) forwards an attribute to the <input>, as Flux forwards what it does not know: the way to name an input that has no label.
In a group, label the group
Ui.Field[Ui.Label["Website"], Ui.InputGroup[…], Ui.Error]: the group stands for its input, so the field's label and error reach it. A neighbour that is not an input (a button, a native select) joins the outline by carrying data-ui-group-target, which the kit's button and select already do.
From Flux
Flux's attributes, as steps:
Flux | What happens |
|---|---|
<flux:input wire:model="email"> | Ui.Input.Bind(() => m.Email) |
wire:model.live, .live.debounce.300ms, wire:model.blur | .Live(), .Debounce(300.Milliseconds), .Blur() |
size="sm", variant="filled" | .Sm, .Filled |
icon="key", icon:trailing="credit-card" | .Icon(Ui.IconName.Key), .IconTrailing(Ui.IconName.CreditCard) |
<x-slot name="iconTrailing"> | .IconTrailing(Ui.Button.Sm.Subtle.Icon(Ui.IconName.XMark)) |
input:class="font-mono" | .InputClass("font-mono") |
as="button" | .As(Ui.InputAs.Button) |
mask:dynamic="$money($input)" | Not built: it is an Alpine expression. |
Reference
Ui.Input
A line of text in a box that holds its icons and buttons. Its type is the model's.
Step | Takes | What it does |
|---|---|---|
.Bind(…) | () => a T | The model's member it reads and writes: () => m.Email. Or Value with OnChange. |
.Value(…) | T, the type it edits | What it holds, for a page that holds it itself. |
.OnChange(…) | a handler given the new value | Runs with the new value when it is committed. |
.AfterBind(…) | a handler given the new value | Runs after Bind has written the model. |
.Validate(…) | a validator | A rule the value is checked by when it is written. |
.Live() | bool | Writes the model as it is typed, after a 150 ms pause: Flux's wire:model.live. |
.Blur() | bool | Writes the model when the reader leaves the field: Flux's wire:model.blur. |
.Debounce(…) | a TimeSpan: 300.Milliseconds | Writes the model once typing has paused this long. |
.OnInput(…) | a handler given the new value | Runs at every keystroke with the text as typed. |
.Label(…) | string | Draws a field around it, with this label and the error. |
.Description(…) | string | Help text between the label and the input. |
.DescriptionTrailing(…) | string | Help text under the input. |
.Placeholder(…) | string | Shown while it is empty. |
.Type(…) | InputTypeText Search Tel Url Email Password Number Checkbox Radio File Range Color Date DatetimeLocal Time Week Month Hidden Button Submit Reset Image Each is a step of its own too, unless an HTML tag has its name. | The input's type: Email, Password, Date, File… The model's type decides when unset. |
.Size(…) | Ui.InputSizeBase Sm Xs Each is a step of its own too, unless an HTML tag has its name. | How tall it is. 40px when unset. |
.Variant(…) | Ui.InputVariantOutline Filled Each is a step of its own too, unless an HTML tag has its name. | How it is drawn. Outlined when unset. |
.Disabled() | bool | Dims it and takes it out of reach. |
.ReadOnly() | bool | May be read and copied, not changed. |
.Invalid() | bool | Error styling the form did not ask for. A bound input is invalid while its member holds a message. |
.Mask(…) | string | Holds each keystroke to a pattern: 9 a digit, a a letter, * either. |
.Icon(…) | a Ui.IconName, or a component | An icon, or content of your own, at the start of the box. |
.IconTrailing(…) | a Ui.IconName, or a component | An icon, or content of your own such as a button, at the end. |
.Kbd(…) | string | A keyboard shortcut shown at the end: "⌘K". |
.Clearable() | bool | A button that empties it, shown while there is text. |
.Copyable() | bool | A button that copies the value. |
.Viewable() | bool | A button that shows a password and hides it again. |
.As(…) | Ui.InputAsInput Button Each is a step of its own too, unless an HTML tag has its name. | Button draws a <button> as the input, pressed with OnClick. |
.OnClick(…) | a handler, with no argument | Runs when an input drawn as a button is pressed. |
.Multiple() | bool | Lets a file input take several files. |
.OnFiles(…) | a handler given the files | Runs with the files chosen in a file input. |
.InputClass(…) | string | Classes for the <input> itself; Class goes on the box around it. |
.Min(…) | string | The input's own min: the lowest number or the first day. |
.Max(…) | string | The input's own max. |
.Step(…) | string | The input's own step. |
.MaxLength(…) | int | The most characters it takes. |
.Autofocus() | bool | Takes the focus when the page opens. |
.Name(…) | string | What it is posted under in a form. |
.Ref(…) | ElementRef | A reference to the <input>, for a call through the browser's APIs. |
.ShowValidation() | bool | False leaves the member's message to a Ui.Error placed elsewhere. |
.Attributes(…) | ("aria-label", "Search") pairs | Attributes for the <input> itself. |
Ui.InputGroup
An input and its neighbours in one outline. In a field, it stands for its input.
No steps of its own: what goes inside it is its content.
Ui.InputGroupPrefix
Words before the input: "https://".
Step | Takes | What it does |
|---|---|---|
.Size(…) | Ui.InputSizeBase Sm Xs Each is a step of its own too, unless an HTML tag has its name. | How tall it is, to match a smaller input. |
Ui.InputGroupSuffix
Words after the input: ".brand.com".
Step | Takes | What it does |
|---|---|---|
.Size(…) | Ui.InputSizeBase Sm Xs Each is a step of its own too, unless an HTML tag has its name. | How tall it is, to match a smaller input. |