OPERATOR

CoordinateInput

Known, unknown, invalid, and disabled

Observation position

Decimal degrees; latitude −90 to 90, longitude −180 to 180.

°
°

Decimal degrees

Position not reported
°

Unknown

°

Unknown

Position unknown

Invalid reported position
°

Enter a value of at most 90 °.

°

Position invalid

Unavailable position
°

Unknown

°

Unknown

Position unknown

const [position, setPosition] = useState<CoordinateValue>({
  latitude: 34.0522,
  longitude: -118.2437,
});
const [invalid, setInvalid] = useState(false);
 
<CoordinateInput
  label="Observation position"
  value={position}
  onValueChange={setPosition}
  onValidationChange={setInvalid}
  latitudeName="latitude"
  longitudeName="longitude"
/>;
 
<Button
  disabled={invalid || position.latitude === null || position.longitude === null}
  onClick={() => requestGoTo(position)}
>
  Go to position
</Button>;

The flat value contains latitude and longitude, each a number or null. A missing axis makes the position unknown. Latitude accepts −90 through 90 and longitude accepts −180 through 180, inclusive. Zero is a known coordinate. Input is decimal degrees; direction letters, DMS, grid references, geocoding, wrapping, and datum conversion are outside this control's contract.

Invalid text remains visible with an explicit Position invalid state. Out-of-range coordinates are never silently clipped or wrapped. Valid axis edits preserve the other axis; invalid drafts do not emit new coordinates. onValidationChange(invalid) reports aggregate validity initially and when it changes, including typed drafts, supplied numeric values, and the caller's error. Use it to block a Go-to or submit action from using the previous valid coordinates while invalid text is visible. Missing axes remain unknown rather than invalid, so require both axes separately when the action needs a complete position. Native form constraints remain available. defaultValue supports uncontrolled use; without it both axes start unknown.

Latitude and longitude sit side by side when the group itself has room for two fields of about 12rem, and stack one above the other when it does not, by the group's own width and never the window's: in a narrow panel on a wide screen they stack, so neither coordinate is cut. What is entered stays as it is across the change.

Props

PROPTYPEDEFAULTREQUIRED
labelstring"Coordinates"no
value

Controlled { latitude: number | null, longitude: number | null }. Latitude is -90…90; longitude is -180…180, inclusive. Null axes are unknown.

node—no
defaultValue

Initial uncontrolled coordinates. Defaults to both axes null.

node—no
onValueChange

(position: CoordinateValue) => void. Only valid axis edits or a cleared null axis are emitted; the other axis is preserved. Does not normalize or wrap out-of-range input.

function—no
onValidationChange

(invalid: boolean) => void. Reports aggregate invalid state initially and on change, including local typed drafts, supplied values and error. Missing/null axes are unknown rather than invalid; callers separately require both axes before Go-to or submission.

function—no
disabledbooleanfalseno
descriptionstring—no
errorstring—no
latitudeName

Native form field name for latitude.

string—no
longitudeName

Native form field name for longitude.

string—no

Accessibility

  • A fieldset names the position; separately labeled latitude and longitude spinbuttons describe their degree units.
  • Tab moves between axes. Arrow keys step, and Home/End select each axis's bounds.
  • The associated status distinguishes known decimal degrees, unknown, and invalid positions in text.
  • Both controls retain visible focus and minimum target geometry; disabled disables both axes and shows them as unavailable, their values still readable.
  • A caller's error is announced as an alert. The legend keeps the case it is written in; the legend, description, status and message wrap.

Manifest entry

<CoordinateInput label="Observation position" value={position} onValueChange={setPosition} />
View agent manifest
{
  "description": "Decimal-degree latitude and longitude fields with bounded validation and explicit unknown or invalid position states.",
  "props": {
    "label": {
      "type": "string",
      "default": "Coordinates"
    },
    "value": {
      "type": "node",
      "description": "Controlled { latitude: number | null, longitude: number | null }. Latitude is -90…90; longitude is -180…180, inclusive. Null axes are unknown."
    },
    "defaultValue": {
      "type": "node",
      "description": "Initial uncontrolled coordinates. Defaults to both axes null."
    },
    "onValueChange": {
      "type": "function",
      "description": "(position: CoordinateValue) => void. Only valid axis edits or a cleared null axis are emitted; the other axis is preserved. Does not normalize or wrap out-of-range input."
    },
    "onValidationChange": {
      "type": "function",
      "description": "(invalid: boolean) => void. Reports aggregate invalid state initially and on change, including local typed drafts, supplied values and error. Missing/null axes are unknown rather than invalid; callers separately require both axes before Go-to or submission."
    },
    "disabled": {
      "type": "boolean",
      "default": false
    },
    "description": {
      "type": "string"
    },
    "error": {
      "type": "string"
    },
    "latitudeName": {
      "type": "string",
      "description": "Native form field name for latitude."
    },
    "longitudeName": {
      "type": "string",
      "description": "Native form field name for longitude."
    }
  },
  "composable_with": [
    "Card",
    "NumberInput"
  ],
  "never_inside": [
    "Button"
  ],
  "example": "<CoordinateInput label=\"Observation position\" value={position} onValueChange={setPosition} />"
}

On this page