A front and back outline of the body for recording where pain or symptoms are. Each region is a checkbox, so it works with a mouse, a touch screen, a keyboard and a screen reader.
Each view is one tab stop. Arrow keys move between regions; Space or Enter selects or clears the focused region.
1 region selected
Installation
In a project set up as in Installation, run:
bunx shadcn@4.22.1 add https://organui.com/r/body-map.jsonOr by name: bunx shadcn@4.22.1 add @organui/body-map.
The source installs into your configured UI directory, along with shadcn's button if you do not have it yet. The body is inline SVG drawn with your theme tokens; there are no images, 3D packages or global styles.
Usage
"use client"
import { useState } from "react"
import { BodyMap } from "@/components/ui/body-map"
export function PainLocation() {
const [regions, setRegions] = useState<string[]>([])
return (
<form action="/symptoms">
<BodyMap
label="Where does it hurt?"
value={regions}
onValueChange={setRegions}
name="regions"
/>
<button type="submit">Save</button>
</form>
)
}Selection is controlled. value is the list of selected region IDs in the order they were picked, and onValueChange receives the next list. IDs that are not among the regions are dropped from that list. With name, each selected ID is submitted as a hidden input, so a plain form posts regions=right-knee®ions=….
Sides are the patient's. From the front, the patient's right is on the viewer's left; from the back it is on the viewer's right. Small R and L markers beside each view say so. Front and back regions have separate IDs, such as right-knee and right-knee-back.
Accessibility
Each view is one tab stop. Arrow keys move to the nearest region in that direction, Home and End jump to the first and last region, and Space or Enter selects or clears the focused region. Regions are announced by name with their checked state, and a status line counts the selection. Selected regions are also listed as buttons that remove them, which is the quickest way to undo a pick on a small screen.
Customize
import { BodyMap, bodyMapRegions } from "@/components/ui/body-map"
// Translate labels, or drop regions you do not record.
const regions = bodyMapRegions
.filter(({ view }) => view === "front")
.map((region) => ({ ...region, label: translate(region.label) }))
<BodyMap regions={regions} views={["front"]} value={value}
onValueChange={(ids) => setValue(ids.slice(-1))} />bodyMapRegions exports the default regions. Pass your own list to regions to rename, remove or redraw them; paths share a 200 by 440 coordinate space and paired shapes set mirrored to reflect across the midline. Use views to show one side, label for the group's accessible name and className for the outer container. To allow one region only, keep the last ID as shown above. Edit the installed source to shade regions by severity.
The outline and regions were drawn for this component as a simplified, non-clinical diagram. They do not follow a validated pain-drawing instrument or dermatome boundaries.
Examples
One View
Show only the front, for forms that ask about one side.
Each view is one tab stop. Arrow keys move between regions; Space or Enter selects or clears the focused region.
No regions selected
API Reference
Import BodyMapProps from the same file as BodyMap.
| Prop | Type | Default |
|---|---|---|
value | readonly string[]The selected region ids. | Required |
onValueChange | (value: string[]) => voidCalled with the new selection. | Required |
regions | readonly BodyMapRegion[]Defaults to bodyMapRegions. Replace labels or shapes to customize. | bodyMapRegions |
views | readonly BodyMapView[]Which figures to show, front and back by default. | ["front", "back"] |
name | stringSubmits one hidden input per selected region inside a form. | — |
label | stringThe accessible name of the map, and its legend. | "Body map" |
className | stringClasses for the root element. | — |