Skip to content
EspañolEnglish
All components (204)

VAT rate

Fields · VatRateSelect

What it is and when to use it

A VAT rate selector: 21 % standard, 10 % reduced, 4 % super-reduced and 0 % exempt.

When to use it

  • Invoice lines, quotes, product records: anywhere a VAT rate is chosen.
  • It delivers the percentage as a number (21), which is what is multiplied later.
  • With rates other rates are offered (a Canary Islands IGIC, a rate from another country): those without a name show only their percentage.

When not to

  • A free percentage: NumberField.

Keyboard: that of Select: Down opens, the arrow keys move through, Enter chooses, Escape closes.

Accessibility: it is a Select, with its label ("VAT rate"), help text and error linked.

Fine styling: no variables of its own: those of Select and the field.

What it looks like

21% · standard
10% · reduced
4% · super-reduced
0% · exempt

Base €129.90 + VAT €27.28 = €157.18.

<MoneyField label="Sale price" value={precio} onChange={setPrecio} max={5000} />
<VatRateSelect value={iva} onChange={setIva} />

Props

Import it with import { VatRateSelect } from '@amset/ui'. Required props are marked.

PropTypeWhat it does
classNamestringMerged with the library classes; it does not replace them.
defaultOpenbooleanThe list starts open. To photograph the panel on the test bench.
disabledboolean—
errorstring—
excludeFromTabOrderbooleanOut of the tab order. It can still be focused by hand (`.focus()`), which is the difference with disabling it: a disabled control cannot be focused in any way.
hiddenLabelbooleanThe label for the screen reader only. For when the field sits inside another one that already explains itself (the prefix of a phone), never to save space: without a visible label you cannot tell what is being chosen.
hintstring—
idstringIt goes to the TRIGGER, which is the focusable node and the one carrying the role of the field: it is the one that must be pointable from outside with an `aria-controls`, a step of a guided tour or a shortcut. The inner parts (the label, the list, the hint) keep their own `id`s, derived from this one when it is passed, so they do not repeat with two selectors on the same page.
initialValuenumber | null—
invalidboolean—
labelstring—
loadingbooleanThe options are arriving. Marks the field as busy, shows a ring and a `loadingText` row in the list; the options that were already there stay.
loadingTextstringWhat the loading row says. "Loading…" by default.
namestringSo that the value travels in a native form.
nativebooleanA SYSTEM `<select>` instead of the own panel. On a phone the operating system's picker appears, which is what is wanted when the list is long or is filled in with a finger. The icons and the two lines per option are lost (`renderOption`, `renderValue`, `icon`, `description`); groups (`group`) come out as `<optgroup>`. Same field on the outside and same props.
offsetnumberSeparation in pixels between the field and the list. None by default.
onBlur() => voidOn leaving the field. The form uses it to mark that it has been touched.
onChange(rate: number | null) => void—
onChangeEvent(event: ChangeEvent<HTMLSelectElement>) => voidWith `native`: the `<select>` event, to migrate a handler that reads `e.target.value`. `onChange` still delivers the key, and both are called.
onOpenChange(open: boolean) => void—
openbooleanOpen or closed, driven from OUTSIDE. With this the state stops living here: whoever passes it decides, and `onOpenChange` notifies of each attempt. It is needed to open the list from somewhere else (a keyboard shortcut, a step of a guided tour) without touching the trigger.
panelWidth"content" | "trigger"How wide the list is. `trigger` (the default) leaves it the same as the field, which is what is expected of a normal selector. `content` lets it grow until the options fit. It is needed when the trigger is deliberately NARROWER than the options: a phone prefix shows "+34" and the list says "+34 · Spain". Measured before this existed: the panel came out 69 px wide and the 19 options appeared cut mid-word ("Spai", "Portu").
placeholderstringText when nothing is chosen. It does not replace the label.
placementPlacementWhere the list appears. By default, attached right below the field.
ratesreadonly number[]The rates on offer, from highest to lowest. By default, those of Spanish VAT (21, 10, 4 and 0).
readOnlyboolean—
renderOptionOptionRender<ListOption>Paints the inside of each option in the list: an avatar, two lines, a price. The name the screen reader reads is still `label`.
renderValue(option: SelectOption) => ReactNodePaints what is chosen inside the closed field. Without this the icon and the label (or `shortLabel`) show. What is announced is what is painted, so it should carry the text.
requiredboolean—
shouldCloseOnSelectbooleanChoosing an option does NOT close the list. By default it closes it, which is what anyone expects in a single-option dropdown; this is for whoever builds on top something that lets you keep looking.
size"sm" | "md" | "lg"—
validationBehavior"aria" | "native"—
valuenumber | nullThe chosen rate (`21`), controlled from outside.
variantFieldVariant—

It also accepts the attributes of the element it renders: className, style, data-*, aria-* and its events.

In the test bench

Every state and variant, to try one at a time, on its page at ui.amset.tech: