Quantity Field
Edit an exact physical quantity and convert compatible display units.
Usage
Length
Enter a length and switch between compatible display units.
Enter a length and switch between compatible display units.
<!-- Quantity Field / Length -->
<script setup lang="ts">
import { QuantityFieldRoot, QuantityFieldInput, QuantityFieldValue, createStandardQuantityPolicies } from '@sectile/vue/quantity-field'
import { Check } from '@lucide/vue'
import {
SelectContent,
SelectItem,
SelectItemIndicator,
SelectRoot,
SelectTrigger,
SelectValue,
} from '@sectile/vue/select'
import { ref } from 'vue'
const policies = { ...createStandardQuantityPolicies('metre', 'metric') }
const units = [
{ id: 'millimetre', label: 'mm' },
{ id: 'centimetre', label: 'cm' },
{ id: 'metre', label: 'm' },
{ id: 'kilometre', label: 'km' },
]
const unitIDs = units.map(({ id }) => id)
const unitLabel = (id: string) => units.find((unit) => unit.id === id)?.label ?? id
const displayUnit = ref('centimetre')
</script>
<template>
<QuantityFieldRoot
v-model:display-unit="displayUnit"
:policies="policies"
:default-value="{ value: '1.25', unit: 'metre' }"
>
<QuantityFieldInput />
<SelectRoot
v-model="displayUnit"
:items="unitIDs"
:text-value="unitLabel"
label="Display unit"
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem v-for="unit in units" :key="unit.id" :value="unit.id">
{{ unit.label }}
<SelectItemIndicator><Check :size="15" aria-hidden="true" /></SelectItemIndicator>
</SelectItem>
</SelectContent>
</SelectRoot>
<QuantityFieldValue />
</QuantityFieldRoot>
</template>Temperature
Convert a temperature between compatible units without changing its physical quantity.
Convert a temperature between compatible units without changing its physical quantity.
<!-- Quantity Field / Temperature -->
<script setup lang="ts">
import { QuantityFieldRoot, QuantityFieldInput, QuantityFieldValue, createStandardQuantityPolicies } from '@sectile/vue/quantity-field'
import { Check } from '@lucide/vue'
import {
SelectContent,
SelectItem,
SelectItemIndicator,
SelectRoot,
SelectTrigger,
SelectValue,
} from '@sectile/vue/select'
import { ref } from 'vue'
const policies = { ...createStandardQuantityPolicies('metre', 'metric') }
const units = [
{ id: 'millimetre', label: 'mm' },
{ id: 'centimetre', label: 'cm' },
{ id: 'metre', label: 'm' },
{ id: 'kilometre', label: 'km' },
]
const unitIDs = units.map(({ id }) => id)
const unitLabel = (id: string) => units.find((unit) => unit.id === id)?.label ?? id
const displayUnit = ref('centimetre')
</script>
<template>
<QuantityFieldRoot
v-model:display-unit="displayUnit"
:policies="policies"
:default-value="{ value: '1.25', unit: 'metre' }"
>
<QuantityFieldInput />
<SelectRoot
v-model="displayUnit"
:items="unitIDs"
:text-value="unitLabel"
label="Display unit"
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem v-for="unit in units" :key="unit.id" :value="unit.id">
{{ unit.label }}
<SelectItemIndicator><Check :size="15" aria-hidden="true" /></SelectItemIndicator>
</SelectItem>
</SelectContent>
</SelectRoot>
<QuantityFieldValue />
</QuantityFieldRoot>
</template>Controlled
Let the parent own the current value and apply accepted changes back to the component.
Let the parent own the current value and apply accepted changes back to the component.
<!-- Quantity Field / Controlled -->
<script setup lang="ts">
import { QuantityFieldRoot, QuantityFieldInput, QuantityFieldValue, createStandardQuantityPolicies } from '@sectile/vue/quantity-field'
import { Check } from '@lucide/vue'
import {
SelectContent,
SelectItem,
SelectItemIndicator,
SelectRoot,
SelectTrigger,
SelectValue,
} from '@sectile/vue/select'
import { ref } from 'vue'
const policies = { ...createStandardQuantityPolicies('metre', 'metric') }
const units = [
{ id: 'millimetre', label: 'mm' },
{ id: 'centimetre', label: 'cm' },
{ id: 'metre', label: 'm' },
{ id: 'kilometre', label: 'km' },
]
const unitIDs = units.map(({ id }) => id)
const unitLabel = (id: string) => units.find((unit) => unit.id === id)?.label ?? id
const displayUnit = ref('centimetre')
</script>
<template>
<QuantityFieldRoot
v-model:display-unit="displayUnit"
:policies="policies"
:default-value="{ value: '1.25', unit: 'metre' }"
>
<QuantityFieldInput />
<SelectRoot
v-model="displayUnit"
:items="unitIDs"
:text-value="unitLabel"
label="Display unit"
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem v-for="unit in units" :key="unit.id" :value="unit.id">
{{ unit.label }}
<SelectItemIndicator><Check :size="15" aria-hidden="true" /></SelectItemIndicator>
</SelectItem>
</SelectContent>
</SelectRoot>
<QuantityFieldValue />
</QuantityFieldRoot>
</template>Examples
Calculator
Enter 50-20% and commit the calculated result as 40.
Enter 50-20% and commit the calculated result as 40.
<!-- Quantity Field / Calculator -->
<script setup lang="ts">
import { QuantityFieldRoot, QuantityFieldInput, QuantityFieldValue, createStandardQuantityPolicies } from '@sectile/vue/quantity-field'
import { createCalculatorExpression } from '@sectile/core/number-field'
import { Check } from '@lucide/vue'
import {
SelectContent,
SelectItem,
SelectItemIndicator,
SelectRoot,
SelectTrigger,
SelectValue,
} from '@sectile/vue/select'
import { ref } from 'vue'
const policies = { ...createStandardQuantityPolicies('metre', 'metric'), evaluator: createCalculatorExpression({ precision: 12, rounding: 'half-even' }) }
const units = [
{ id: 'millimetre', label: 'mm' },
{ id: 'centimetre', label: 'cm' },
{ id: 'metre', label: 'm' },
{ id: 'kilometre', label: 'km' },
]
const unitIDs = units.map(({ id }) => id)
const unitLabel = (id: string) => units.find((unit) => unit.id === id)?.label ?? id
const displayUnit = ref('centimetre')
</script>
<template>
<QuantityFieldRoot
v-model:display-unit="displayUnit"
:policies="policies"
:default-value="{ value: '1.25', unit: 'metre' }"
>
<QuantityFieldInput />
<SelectRoot
v-model="displayUnit"
:items="unitIDs"
:text-value="unitLabel"
label="Display unit"
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem v-for="unit in units" :key="unit.id" :value="unit.id">
{{ unit.label }}
<SelectItemIndicator><Check :size="15" aria-hidden="true" /></SelectItemIndicator>
</SelectItem>
</SelectContent>
</SelectRoot>
<QuantityFieldValue />
</QuantityFieldRoot>
</template>Compound
Parse a compound unit while preserving one canonical quantity.
Parse a compound unit while preserving one canonical quantity.
<!-- Quantity Field / Compound -->
<script setup lang="ts">
import { QuantityFieldRoot, QuantityFieldInput, QuantityFieldValue, createStandardQuantityPolicies } from '@sectile/vue/quantity-field'
import { createCalculatorExpression } from '@sectile/core/number-field'
import { Check } from '@lucide/vue'
import {
SelectContent,
SelectItem,
SelectItemIndicator,
SelectRoot,
SelectTrigger,
SelectValue,
} from '@sectile/vue/select'
import { ref } from 'vue'
const policies = { ...createStandardQuantityPolicies('metre', 'metric'), evaluator: createCalculatorExpression({ precision: 12, rounding: 'half-even' }) }
const units = [
{ id: 'millimetre', label: 'mm' },
{ id: 'centimetre', label: 'cm' },
{ id: 'metre', label: 'm' },
{ id: 'kilometre', label: 'km' },
]
const unitIDs = units.map(({ id }) => id)
const unitLabel = (id: string) => units.find((unit) => unit.id === id)?.label ?? id
const displayUnit = ref('centimetre')
</script>
<template>
<QuantityFieldRoot
v-model:display-unit="displayUnit"
:policies="policies"
:default-value="{ value: '1.25', unit: 'metre' }"
>
<QuantityFieldInput />
<SelectRoot
v-model="displayUnit"
:items="unitIDs"
:text-value="unitLabel"
label="Display unit"
>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem v-for="unit in units" :key="unit.id" :value="unit.id">
{{ unit.label }}
<SelectItemIndicator><Check :size="15" aria-hidden="true" /></SelectItemIndicator>
</SelectItem>
</SelectContent>
</SelectRoot>
<QuantityFieldValue />
</QuantityFieldRoot>
</template>API
Vue package: @sectile/vue/quantity-field
QuantityFieldRootQuantityFieldInputQuantityFieldUnitSelectQuantityFieldValue
Functions
createStandardQuantityPolicies
function createStandardQuantityPolicies(canonicalUnit: string, unitSystem?: StandardQuantityUnitSystem): QuantityFieldPoliciesProps
QuantityFieldRootProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
defaultDisplayUnitDisplay unit used before the component receives a controlled unit.
defaultValueInitial value used when the component owns its state.
disabledWhether interaction is unavailable.
displayUnitControlled unit used to present the stored quantity.
labelAccessible name announced for the control.
modelValueCurrent value when state is controlled by the parent.
policiesBehavior policies that customize validation, movement, or selection.
readonlyWhether the value can be inspected but not changed.
QuantityFieldInputProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
formID of the native form associated with the control.
nameName used for native form submission.
requiredWhether the control must contain a valid value before submission.
QuantityFieldPartProps
asElement or component rendered for this part.
asChildWhether to merge this part into its single child instead of rendering a wrapper.
Slots
QuantityFieldRootSlotProps
disabledWhether interaction is unavailable.
displayUnitUnit currently used for presentation.
invalidWhether the current draft fails validation.
readonlyWhether the value can be inspected but not changed.
textFormatted text for the current value.
valueCurrent value exposed by this contract.
Events
QuantityFieldRoot
commitEmitted when the current draft is committed.
update:displayUnitEmitted when the requested display unit changes.
update:modelValueEmitted when the component requests a new controlled value.
Other types
StandardQuantityUnitSystem
type StandardQuantityUnitSystem = 'metric' | 'imperial' | 'all'QuantityFieldValueChangeHandler
type QuantityFieldValueChangeHandler = (value: QuantityValue | null) => voidQuantityFieldDisplayUnitChangeHandler
type QuantityFieldDisplayUnitChangeHandler = (value: string) => voidQuantityFieldCommitHandler
type QuantityFieldCommitHandler = (details: { value: QuantityValue | null; expression: string; displayUnit: string }) => voidParts
Shared scope: [data-scope="quantity-field"]. Combine it with a part selector to keep styles local to this component.
| Part | Selector | Role | Extra attributes |
|---|---|---|---|
root | [data-part="root"] | Defines the component boundary and owns its composed parts. | — |
input | [data-part="input"] | Accepts the editable value or draft. | — |
unit-select | [data-part="unit-select"] | Chooses the unit applied to the numeric value. | — |
value | [data-part="value"] | Displays the current committed value. | — |
Keyboard interaction
| Key | Behavior |
|---|---|
| Standard editing keys | Edit and select text with the host input conventions. |
| Tab | Commit focus movement without replacing native text behavior. |
Accessibility
The labeled input exposes the accepted quantity while unit selection and formatted output remain separately identifiable.
