Magic text box
A multi-line field that refuses to push the rest of the page around. It holds itself to a few lines, and grows a toggle only when there is genuinely more text than fits, so a short value looks like an ordinary text box and a long one stays out of the way until asked.
It opens only when there is something to open
Both fields are configured identically. The first has more text than three lines hold, so it offers a toggle, the second does not, and so does not. The measurement is the browser's, taken after the first paint - which is why the toggle arrives a frame late rather than being decided from the string's length, a number that tells you nothing about how it wraps.
<VetoMagicTextBox TValue="string" Label="Release notes" @bind-Value="_notes" />The copy button
It rests hidden and arrives on hover, because on most fields it is an occasional convenience rather than part of the furniture. Always is for the field whose whole point is to be copied - a generated key, a connection string - where hiding the button until hover hides the feature. Either way it also appears on keyboard focus: revealing on hover alone would put it out of reach of anyone not using a pointer. Copying raises a toast, because the icon swapping to a tick is small and easy to miss when the reader's eye is on the text they just copied; NotifyOnCopy turns that off, and a field in an app with no toast host simply stays quiet.
@* Hover and keyboard focus reveal it - the default. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text" />
@* Always visible, for a value that exists to be copied. *@
<VetoMagicTextBox TValue="string" @bind-Value="_key"
CopyReveal="VetoCopyReveal.Always" />
@* No copy button at all. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text" Copyable="false" />Corner slots
Your own buttons go in either corner, laid over the field's top edge. The field reserves a gutter on whichever side has content, so no line of text ever runs underneath - and only the side in use is indented, so a field with nothing in its leading corner is not needlessly narrow. Corner content is always visible; only the copy button follows CopyReveal.
<VetoMagicTextBox TValue="string" Label="Message" @bind-Value="_message">
<LeadingContent>
<VetoIconButton Icon="@VetoIcons.Bold" Label="Bold" Size="VetoSize.Small" />
</LeadingContent>
<TrailingContent>
<VetoIconButton Icon="@VetoIcons.Trash" Label="Clear" Size="VetoSize.Small"
OnClick='() => _message = string.Empty' />
</TrailingContent>
</VetoMagicTextBox>How far it opens
CollapsedLines sets the resting height and is what the overflow measurement is taken against. ExpandedLines caps how far it opens, for a field that could otherwise unroll to the length of the page; leave it null and the field grows to fit whatever is in it.
@* Two lines at rest, opening to fit the whole value. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text" CollapsedLines="2" />
@* Two lines at rest, opening no further than four. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text"
CollapsedLines="2" ExpandedLines="4" />A length limit
MaxLength caps what can be typed and ShowCounter counts it as the user types, before the value is bound.
<VetoMagicTextBox TValue="string" Label="Post"
MaxLength="280" ShowCounter="true" @bind-Value="_post" />Bound open state
Expanded is two-way bindable, so the page can open the field itself - to reveal the one that failed validation, say - and stays told when the reader closes it again.
<VetoMagicTextBox TValue="string" Label="Stack trace"
@bind-Value="_trace" @bind-Expanded="_expanded" />
<VetoButton OnClick="() => _expanded = !_expanded">Toggle from the page</VetoButton>Border, and how fast it moves
No border by default: the field is usually one of several in a form that already groups them, and a box that changes height draws more attention to the change when it is outlined. Bordered puts it back for a field standing alone. ExpandDuration sets how long opening takes - long enough to follow by eye is the point, because the movement is what tells the reader the text was folded rather than cut off. Anyone who has asked for reduced motion gets the new height immediately instead.
@* No border by default. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text" />
@* Outlined, and slower to open. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text"
Bordered="true" ExpandDuration="1200ms" />Fixed height, and the usual field states
Expandable=false keeps the field at its resting height and renders no toggle - the text still scrolls, it simply never rearranges the page. Disabled and read-only behave as they do on any Veto input.
@* Holds its height and never offers a toggle; the text scrolls instead. *@
<VetoMagicTextBox TValue="string" @bind-Value="_text" Expandable="false" />
<VetoMagicTextBox TValue="string" @bind-Value="_text" ReadOnly="true" />
<VetoMagicTextBox TValue="string" @bind-Value="_text" Disabled="true" />