npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-native-body-parts-anatomy

v1.2.0

Published

Interactive React Native body diagram — 317 individually tappable muscle fragments across 23 muscle groups, male/female, front/back. Pinch-zoom, pan, whole-muscle mode, fully themeable, zero native code.

Readme

react-native-body-parts-anatomy

npm version npm downloads CI License: MIT

An interactive human body diagram for React Native where every muscle is individually addressable — 23 muscle groups split into 317 separately tappable SVG fragments, across male/female and front/back. Zero native code.

▶ Try it live — one click opens a Snack running the full demo, with the dependencies and the iOS preview already set up. Nothing to install, nothing to paste.

Use the iOS or Android tab, not Web. Snack opens on Web by default, and selection does not work there — see Platform support.

In the picker above, two separate abdominal fragments are selected — not the whole abdomen. That per-fragment granularity is the point of this library.

Why this library

Most React Native body maps expose one tappable path per muscle group. That is the right model for "which muscle did you train?" and the wrong one as soon as you need to know where within a muscle something happened — which is exactly what pain tracking, physiotherapy, injury logs, and symptom diaries are made of.

| Capability | What you get | |---|---| | Tappable targets | 317 fragments across 23 muscle groups — abs alone is 8 separate targets per gender on the front view | | Two selection models | Fragment mode (default) addresses every piece individually; selectByGroup swaps in whole-muscle slugs so one tap selects the entire muscle | | Sub-muscle location | Every fragment carries a derived side (left/right) and axis (upper/middle/lower/inner/center/outer), so you can label "lower-left abs" without a lookup table of your own | | Views | Male/female × front/back — 4 combinations | | Zoom & pan | Pinch, drag-while-zoomed, and step +/− buttons, built in | | Selection accuracy | Selection commits on a tap, so a drag or scroll starting over a muscle never records it — see Interaction model | | Theming | Every colour is a prop. No bundled theme system, no i18n framework assumed | | Dependencies | Zero native code — pure JS/TSX over react-native-svg, react-native-gesture-handler, and react-native-reanimated | | Accessibility | Every muscle is individually reachable and selectable with VoiceOver/TalkBack, announced by name and selected state. Honors OS Reduce Motion. See Accessibility |

When not to use it: if you only need "which muscle group", a simpler body map is a lighter dependency. Reach for this one when the sub-region actually matters.

Installation

npm install react-native-body-parts-anatomy
npx expo install react-native-svg react-native-gesture-handler react-native-reanimated

Required setup

Wrap your app root in GestureHandlerRootView. Both pinch/pan and tap selection run through a GestureDetector, which throws at runtime without it:

import { GestureHandlerRootView } from 'react-native-gesture-handler';

export default function App() {
  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      {/* ...your app... */}
    </GestureHandlerRootView>
  );
}

You can skip this only if every diagram you render is read-only — that is, you never pass onFragmentPress. In that case no gesture detector is mounted at all. Note this depends on onFragmentPress, not on zoomable: a zoomable={false} diagram that is still tappable needs GestureHandlerRootView.

On the bare React Native CLI (not Expo), also follow each peer's own native setup — notably react-native-reanimated's Babel plugin. Those are the peers' setup steps, not this package's.

Reanimated v4 users: Reanimated 4 moved worklets into a separate react-native-worklets package and requires the New Architecture. This package never imports react-native-worklets directly, so it isn't declared as a peer here — but you must satisfy Reanimated's own peer requirement, and keep a single react-native-worklets version in your tree. A mismatch between the installed JS version and the Babel plugin version fails at startup with [Worklets] Mismatch between JavaScript code version and Worklets Babel plugin version.

Interaction model

Selection is committed by a tap gesture, not by the raw touch-down on an SVG path. This matters because a body diagram is usually both a picker and something you drag:

  • Tap (finger travels <10pt, released within 1.5s) → onFragmentPress(slug).
  • Drag starting on a fragment → pans the zoomed diagram, or scrolls the parent list, and selects nothing.
  • Tap on empty space between fragments → selects nothing.
  • At 1× the pan gesture never activates, so the diagram does not block a surrounding ScrollView. It claims drags only once zoomed in.

Committing on touch-down instead (the obvious approach, and what most SVG body maps do) means every attempt to pan or scroll silently records a body part the user never chose. Committing on React Native's onPress is not an option either — Fabric cancels it on the slightest finger movement, so real taps stop registering.

Quick start

Interactive picker

import { useState } from 'react';
import { BodySilhouette } from 'react-native-body-parts-anatomy';

function PainAreaPicker() {
  const [selectedSlugs, setSelectedSlugs] = useState<string[]>([]);

  const toggleFragment = (slug: string) => {
    setSelectedSlugs((current) =>
      current.includes(slug) ? current.filter((item) => item !== slug) : [...current, slug]
    );
  };

  return (
    <BodySilhouette
      gender="male"
      view="front"
      selectedSlugs={selectedSlugs}
      onFragmentPress={toggleFragment}
    />
  );
}

Read-only diagram

colorForSlug is called for every fragment, so a severity map needs no selection state at all — return undefined for anything you have no colour for:

import { BodySilhouette } from 'react-native-body-parts-anatomy';

function SymptomDiagram({ colorBySlug }: { colorBySlug: Map<string, string> }) {
  return (
    <BodySilhouette
      gender="female"
      view="back"
      zoomable={false}
      selectedSlugs={[]}
      colorForSlug={(slug) => colorBySlug.get(slug)}
    />
  );
}

Whole-muscle picker

Set selectByGroup when per-piece granularity is more than you need. Selection identifiers become muscle-group slugs ('abs', 'chest') instead of fragment slugs, and every piece of a muscle commits the same identifier — so toggling works no matter which piece was tapped:

function MusclePicker() {
  const [groups, setGroups] = useState<string[]>([]);

  return (
    <BodySilhouette
      gender="male"
      view="front"
      selectByGroup
      selectedSlugs={groups}
      onFragmentPress={(groupSlug) => {
        setGroups((current) =>
          current.includes(groupSlug)
            ? current.filter((item) => item !== groupSlug)
            : [...current, groupSlug]
        );
      }}
    />
  );
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | gender | 'male' \| 'female' | — (required) | | | view | 'front' \| 'back' | — (required) | | | selectedSlugs | readonly BodySlugInput[] | — (required) | Identifiers currently highlighted — fragment slugs by default, muscle-group slugs under selectByGroup. Plain string[] is accepted. | | selectByGroup | boolean | false | Select whole muscles: onFragmentPress reports the tapped fragment's parent group. | | colorForSlug | (slug: BodySlug) => string \| undefined | — | Fill for any fragment, selected or not. Receives the identifier matching the active mode — the group slug under selectByGroup, the fragment slug otherwise. Falls back to selectedFragmentColor / unselectedFragmentColor when it returns undefined. | | onFragmentPress | (slug: string) => void | — | Omit for a read-only diagram. | | zoomable | boolean | true | Enables pinch/pan/step-zoom and the floating +/− buttons. | | style | StyleProp<ViewStyle> | — | Applied to the outer container after the built-in width/aspect-ratio rules, so you can set margins, a background, or borderRadius without a wrapper View. | | testID | string | — | Also derives per-fragment IDs (${testID}-abs-male-front-1) and ${testID}-zoom-in / -zoom-out, so Detox/Maestro can target individual muscles. | | accessibilityMode | 'auto' \| 'fragments' \| 'groups' \| 'diagram' \| 'none' | 'auto' | How the diagram presents itself to VoiceOver/TalkBack — see Accessibility. | | accessibilityLabel | string | derived | Overrides the spoken description of the diagram as a whole. | | fragmentAccessibilityHint | string | — | Spoken after each muscle's name, e.g. 'Double tap to select'. | | labels | Partial<Record<GroupSlug, string>> | — | Localized muscle-group names, used for spoken labels. | | hitTolerance | number | 0 | How far, in points, a tap may miss every fragment and still select the nearest one. Off by default; 12 suits the small fragments this library is made of. | | onPressMiss | () => void | — | Called when a tap selected nothing, even after hitTolerance. | | onZoomChange | (scale: number) => void | — | Called whenever the zoom level settles on a new value. | | resetZoomOnViewChange | boolean | true | Reset zoom and pan when gender or view changes. | | outlineColor | string | '#333333' | Skin-outline stroke color. | | unselectedFragmentColor | string | '#D6D6D6' | Fill for fragments not in selectedSlugs. | | selectedFragmentColor | string | '#2563EB' | Fallback fill when colorForSlug is omitted or returns undefined. | | zoomButtonBackgroundColor | string | '#FFFFFF' | | | zoomButtonBorderColor | string | '#D6D6D6' | | | zoomButtonIconColor | string | '#333333' | | | zoomInAccessibilityLabel | string | 'Zoom in' | | | zoomOutAccessibilityLabel | string | 'Zoom out' | | | minScale | number | 1 | | | maxScale | number | 3 | | | zoomStep | number | 0.5 | Scale delta per tap of a zoom button. | | zoomAnimationDurationMs | number | 200 | |

Labels

Every slug has a display name, so you do not have to maintain a name table of your own:

import {
  MUSCLE_GROUPS,
  labelForSlug,
  labelForFragment,
  describeFragment,
} from 'react-native-body-parts-anatomy';

labelForFragment('abs-male-front-1'); // 'Upper left abdominals'
labelForFragment('chest-male-front-1'); // 'Left chest'
labelForFragment('head-male-front-1'); // 'Head'

// Handles either selection mode without being told which one produced the slug.
labelForSlug('abs'); // 'Abdominals'
labelForSlug('abs-male-front-1'); // 'Upper left abdominals'

// The pieces separately, when you want to build your own string.
describeFragment('abs-male-front-1');
// { group: 'abs', groupLabel: 'Abdominals', side: 'left', axis: 'upper',
//   label: 'Upper left abdominals' }

// Everything you need for a legend, a picker, or a settings list.
MUSCLE_GROUPS; // [{ slug: 'abs', label: 'Abdominals', fragmentCount: 16 }, ...]

To localize, pass an override map — you only supply what you translate:

const labels = { 'abs': 'Abdominaux', 'chest': 'Poitrine' };
labelForFragment('abs-male-front-1', labels); // 'Upper left abdominaux'

Exported data & types

import {
  BODY_REGIONS,
  FRAGMENTS_BY_PARENT,
  FRAGMENT_BY_SLUG,
  GROUP_SLUGS,
  fragmentSlugsForGroup,
  selectionKeyForFragment,
  isFragmentSelected,
} from 'react-native-body-parts-anatomy';
import type {
  BodyFragment,
  BodyRegionSet,
  BodySlug,
  FragmentSlug,
  GroupSlug,
} from 'react-native-body-parts-anatomy';

// Every muscle-group slug, sorted — the valid keys under `selectByGroup`.
GROUP_SLUGS; // ['abs', 'adductors', 'ankles', ... 23 total]

// The full fragment tree, if you need to iterate it yourself.
BODY_REGIONS.male.front.fragments; // BodyFragment[]

// Reverse lookup: parent muscle name -> every fragment slug under it, across
// all four gender x view sets (e.g. legacy-data migration). For one view only,
// use `fragmentSlugsForGroup` below.
FRAGMENTS_BY_PARENT['abs']; // ["abs-male-front-1", "abs-female-front-1", ...]

// O(1) lookup by fragment slug.
const fragment = FRAGMENT_BY_SLUG['abs-male-front-3'];
// { slug, parentSlug: 'abs', side: 'left', axis: 'upper', pathData }

// Group helpers backing `selectByGroup`, exported for custom logic too:
fragmentSlugsForGroup('abs', 'male', 'front'); // ["abs-male-front-1", ..., "abs-male-front-8"]
selectionKeyForFragment('abs-male-front-3', true); // 'abs'
isFragmentSelected(fragment, ['abs'], true); // true

GroupSlug (23 members), FragmentSlug (317), and BodySlug (either) are literal-union types, so your editor autocompletes slugs and a typo in a switch or comparison is a compile error. Props still accept plain string, so existing useState<string[]> code keeps working — and in development, a slug that can never match is reported with a console warning naming the exact problem.

Controlling zoom, and forgiving taps

Many fragments are only a few points across, so a near miss is the common case rather than a deliberate tap on the background. hitTolerance snaps such a tap to the nearest fragment; onPressMiss tells you when even that found nothing:

<BodySilhouette
  gender="male"
  view="front"
  selectedSlugs={selected}
  onFragmentPress={toggle}
  hitTolerance={12}
  onPressMiss={() => setHint('Try zooming in to pick a smaller area')}
/>

It is off by default because "a tap on empty space selects nothing" is documented behaviour that existing apps may rely on — see Interaction model. Opt in and the guarantee becomes "a tap more than hitTolerance from any fragment selects nothing".

Zoom is drivable from the outside through a ref:

import { useRef } from 'react';
import { BodySilhouette, type BodySilhouetteHandle } from 'react-native-body-parts-anatomy';

const diagram = useRef<BodySilhouetteHandle>(null);

<BodySilhouette ref={diagram} {...props} onZoomChange={setScale} />;

diagram.current?.resetZoom();
diagram.current?.zoomTo(2);
diagram.current?.zoomToGroup('chest'); // frames the muscle, within maxScale
diagram.current?.getScale();

Every method takes an optional { animated } (default true, and always instant under the OS Reduce Motion setting). zoomTo clamps to minScale/maxScale, and zoomToGroup ignores a group that has no fragments in the current view rather than throwing.

Accessibility

Every muscle is individually reachable and selectable with VoiceOver and TalkBack. Announced names come from the same label helpers documented above, so a localized labels map localizes the screen reader too.

<BodySilhouette
  gender="male"
  view="front"
  selectedSlugs={selected}
  onFragmentPress={toggle}
  fragmentAccessibilityHint="Double tap to select"
/>

accessibilityMode controls what a screen reader sees:

| Mode | Behaviour | |---|---| | auto (default) | groups under selectByGroup, fragments when tappable, diagram when read-only | | fragments | Every individual piece is focusable and selectable | | groups | One entry per muscle per side — "Left abdominals", "Right abdominals" — each selecting the whole muscle | | diagram | A single element describing the whole picture. Right for read-only diagrams, where hundreds of stops would be noise | | none | Hidden from assistive tech entirely |

How it works, and why it matters

react-native-svg's Path accepts only accessible, accessibilityLabel, and testID — a role, a selected state, and an activation handler are all dropped. So a screen reader could hear a muscle's name but never its state, and could never select it.

This library therefore renders an overlay of real, positioned views carrying the full accessibility contract, mounted only while a screen reader is actually running. When one is not, the overlay does not exist and gesture behaviour is byte-for-byte what it was — that guarantee is covered by tests.

In groups mode each muscle is split into a left and a right rectangle rather than one merged box. Merging a bilateral muscle produces a band spanning the entire body — the "forearms" rectangle would cover the abdomen — so exploring by touch would report whatever muscle owned the widest band instead of what is under the finger.

Platform support

| Platform | Renders | Selection | Zoom & pan | |---|---|---|---| | iOS | yes | yes | yes | | Android | yes | yes | yes | | Web (react-native-web) | yes | no | pinch/pan unverified |

Web is not supported. react-native-svg drops the onPressIn handler this library uses to detect which fragment was touched — the browser console shows Unknown event handler property 'onPressIn'. It will be ignored. — so a web build renders the diagram correctly but is effectively read-only. This is a deliberate scope decision, not a pending fix: use iOS or Android for interactive diagrams.

Regenerating the data (maintainers only)

src/data/bodyRegions.generated.ts is generated, not hand-written, by scripts/generate-body-regions.mjs, which reads react-native-body-highlighter's raw SVG path data. This script is repo-only — it is not part of the published package (see the files allowlist in package.json) and pulls in svgpath as a devDependency only it uses.

npm run generate-data

This temporarily installs [email protected] (--no-save), runs the codegen, then leaves your package.json untouched. See THIRD_PARTY_NOTICES.md for why this package depends on that library's data at build time but never at runtime.

License & attribution

MIT — see LICENSE.

The fragment path data is derived from react-native-body-highlighter (MIT, ELABBASSI Hicham) — see THIRD_PARTY_NOTICES.md for the full attribution.

Contributing


Made with create-react-native-library