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

@axonpack/react-pretty-print

v0.1.2

Published

Interactive JSON tree for React and React Native — you supply the View and Text primitives

Readme

@axonpack/react-pretty-print

Collapsible JSON and XML trees and a syntax highlighter for React and React Native from one implementation. You pass the container, text and pressable components in; the package owns the logic — expansion state, array chunking, collapsed-node previews, an XML parser, a 38-language tokenizer and 130 palettes.

No dependencies. Nothing in it imports react-native or touches the DOM, so a web project installs nothing but React and configures no bundler alias, and a React Native project passes its own components straight in with no adapter to write.

Getting started

npm install @axonpack/react-pretty-print
# or: bun add @axonpack/react-pretty-print

React 18 or newer is a peer dependency. There is no native module, so no pod install and no prebuild — it runs in Expo Go.

Usage

On the web, domPrimitives ships with the package:

import { CodeHighlight, JsonTree, XmlTree, domPrimitives } from '@axonpack/react-pretty-print';
import { LIGHT_THEME } from '@axonpack/react-pretty-print/themes';

const Example = () => (
  <>
    <JsonTree primitives={domPrimitives} value={response} theme={LIGHT_THEME} />
    <XmlTree primitives={domPrimitives} source={feed} theme={LIGHT_THEME} />
    <CodeHighlight
      primitives={domPrimitives}
      code={src}
      language="typescript"
      theme={LIGHT_THEME}
    />
  </>
);

On React Native, react-native's own components satisfy the contract as-is:

import { Pressable, Text, View } from 'react-native';
import { JsonTree } from '@axonpack/react-pretty-print';

const Example = () => <JsonTree primitives={{ View, Text, Pressable }} value={response} />;

See example-web/ and example-native/ for both, including the context menu, a theme picker and a language picker.


<JsonTree />

A value as a tree that expands and collapses, with a preview on every closed node.

primitives

Required. { View, Text, Pressable } — see Primitives.

value

Required. A JsonValue: a string, number, boolean, null, an array of those, or an object of them. Parse a response body before handing it over; this renders a value, it does not accept text.

rootLabel

Optional. A label for the root row, e.g. "response". Omitted, the root renders as its own preview with no key.

theme

Optional, defaults to DARK_THEME. Any PrettyPrintTheme — see Themes.

defaultExpanded

Optional, default true. Whether the root starts open. Its children always start closed: a hundred-entry payload opened all the way is a wall.

onCopy

Optional. (text: string) => void. The clipboard is platform-specific — react-native's is a separate install, the web's is on navigator — so the package takes a function. Without it, the copy actions are not offered at all. Pass Clipboard.setStringAsync or navigator.clipboard.writeText.

onRequestMenu

Optional. (items: MenuItem[], event: unknown) => void, fired on long-press (React Native) or right-click (web). Enables the gesture; without it, no menu is requested. See The context menu.

matcher

Optional. A Matcher — see Search. Drives highlighting and expansion: the branches holding a match open, everything else collapses.


<XmlTree />

A document as a tree. Elements expand and collapse; text and character data are leaves.

primitives

Required. As above.

source

Required. The raw XML as a string. Parsed in JS, because React Native has no DOMParser. A document that will not parse renders a one-line reason instead — it never throws, so keep the raw text around to show alongside it.

theme

Optional, defaults to DARK_THEME.

matcher

Optional. A Matcher — see Search. Matches element names, attribute names, attribute values, text and character data. An element opens only for a match inside it: one matching on its own name or attributes is painted where it sits, since that says nothing about whether its contents are worth unfolding.


<CodeHighlight />

Highlighted source, one node per token.

primitives

Required. As above.

code

Required. The source as a string.

language

Required. One of the 38 in Languages. Use detectLanguage() if you have a content type rather than a known language.

theme

Optional, defaults to DARK_THEME.

format

Optional, default true. Re-indents minified source before highlighting, which is the difference between a readable listing and one endless line. It only applies to languages whose structure is punctuation — see Languages for which — and it is a re-indenter, not a pretty-printer: it breaks lines on braces, brackets and statement terminators and spaces separators, but it will not insert a space after a keyword or invent a terminator the source omitted. Turn it off for source you formatted yourself.

maxHighlightLength

Optional, defaults to MAX_HIGHLIGHT_LENGTH (50,000). Above this many characters the source renders as one unstyled block — nothing is truncated, only the highlighting is skipped. Raise it if you'd rather wait, or pass Infinity to remove the cap:

<CodeHighlight
  primitives={domPrimitives}
  code={bundle}
  language="javascript"
  maxHighlightLength={Infinity}
/>

Tokenizing walks the string once per rule per position, so cost grows with length times the size of the language's rule table — a megabyte of minified source is enough to block the thread. That is the trade you're making when you raise it.

matcher

Optional. A Matcher — see Search. Matched runs are painted, including a match that spans a token boundary: matching happens once over the whole string and the ranges are clipped per token, so x=1 highlights across the identifier, the operator and the number.

Nothing here mounts a scroller. A long line needs one, and only you know whether the block is already inside a ScrollView or a container with overflow-x.


Primitives

Every renderer takes its components from you. That is what lets one implementation serve both platforms.

type Primitives = {
  View: ComponentType<{ style?; children? }>;
  Text: ComponentType<{ style?; children?; selectable? }>;
  Pressable: ComponentType<{ style?; children?; onPress?; onLongPress? }>;
};

| Platform | What to pass | | ------------ | ---------------------------------------------------------------------------- | | React Native | { View, Text, Pressable } imported from react-native — no wrapper needed | | The DOM | domPrimitives, exported from this package |

selectable exists for React Native only, where text is unselectable by default and a code block you cannot copy from is useless; domPrimitives drops it rather than forwarding an unknown attribute to a <span>.

The style types are deliberately loose: React Native's StyleProp<ViewStyle> and React's CSSProperties are not assignable to each other in either direction, so one concrete type would reject a platform outright. The long-press event is unknown and passed through untouched for the same reason — anything narrower fails contravariance and would force every React Native consumer to write a wrapper.

A custom set is a few lines. Anything that accepts a style object and renders children will do.

The context menu

The package computes which actions apply to a node and what each does to the expansion state; you render the popover. It cannot ship one: a floating menu has to escape its scroll container, which needs a Modal on React Native and a portal or position: fixed on the DOM — React Native has no portal and the DOM has no modal, so there is no shared subset to write against.

const [menu, setMenu] = useState<{ items: MenuItem[]; x: number; y: number } | null>(null);

<JsonTree
  primitives={{ View, Text, Pressable }}
  value={response}
  onCopy={Clipboard.setStringAsync}
  onRequestMenu={(items, event) => {
    const { pageX, pageY } = (event as GestureResponderEvent).nativeEvent;
    setMenu({ items, x: pageX, y: pageY });
  }}
/>;

MenuItem is { label: string; onSelect: () => void; copyText?: string }. Render the labels and call onSelect on press. A copy item also carries copyText, the text it copies, for a host that has to do the copying somewhere onSelect does not run. The items offered depend on the node: Copy value or Copy object when onCopy is set, plus Expand / Collapse, Expand recursively and Collapse recursively on a container that has children.

event is unknown because the package never reads it. Cast it at this one call site: nativeEvent.pageX on React Native, clientX on a DOM mouse event.

example-native/components/ContextMenu.tsx and example-web/src/components/ContextMenu.tsx are working implementations of each — copy one.

Search

All three renderers take a matcher, which is plain data rather than a callback:

type Matcher = { pattern: RegExp | null; invalid: boolean };

That shape is the contract. If your app already compiles a matcher for its own list filtering, pass it straight in — TypeScript matches structurally, so there is nothing to import and nothing to convert. If you don't have one, buildMatcher compiles a query:

import { buildMatcher, DEFAULT_SEARCH_MODES } from '@axonpack/react-pretty-print';

const matcher = buildMatcher({ text: query, ...DEFAULT_SEARCH_MODES, matchCase: false });

<JsonTree primitives={domPrimitives} value={data} matcher={matcher} theme={theme} />;

SearchModes are matchCase, wholeWord and regex. Two behaviours worth knowing:

  • An uncompilable pattern is no search, not no results. A half-typed regex gives { pattern: null, invalid: true }, and the tree keeps its normal expansion rather than collapsing to nothing. Read invalid if you want to mark the input.
  • A closed node still previews its contents. The search walk looks at the leaves underneath it, so a branch with a match opens; a branch without one stays closed and shows its preview as usual.

The band's colour is the matchHighlight token, translucent so the matched text keeps its syntax colour. findMatches, clipMatches, splitByMatches and testMatch are exported for building your own row filters against the same matcher.

Both examples have a search bar at the top with the three mode toggles wired up — example-web/src/components/SearchBar.tsx and example-native/components/SearchBar.tsx — including the invalid-pattern state.

Themes

A palette is a flat set of colour roles, never a stylesheet, so nothing has to be translated between platforms.

import { AZURE_DARK_VIVID_THEME, DARK_THEME } from '@axonpack/react-pretty-print/themes';
import type { PrettyPrintTheme } from '@axonpack/react-pretty-print/themes';

They are a separate entry point on purpose: 130 palettes is ~52KB, and a project that only wants a renderer shouldn't pull them into its module graph to find out it didn't need them.

| Token | Paints | | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | background | The surface the palette was designed against. No renderer draws it — you own your container — but it is stated so you know what you were handed. | | text | Unclassified code text | | key | JSON keys, XML element names | | string | Quoted strings, XML attribute values | | number / boolean | Literals. They share a colour in every shipped palette: painting 2 and true differently is noise. | | null | An absent value | | punctuation | Braces, separators, collapsed-node previews | | toggle | The expand arrow | | keyword | Language keywords | | comment | Comments | | accent | Function names, CSS properties, attribute names | | tag | Markup tags, CSS selectors | | fontFamily / fontSize | Type. Monospace at 12 in every shipped palette. |

DARK_THEME and LIGHT_THEME are the neutral pair the renderers default to — the only palettes with no hue cast. The other 128 are named <hue>_<mode>_<character>_THEME: 16 hues (crimson, amber, gold, citron, lime, emerald, jade, teal, cyan, azure, cobalt, sapphire, indigo, violet, magenta, rose) × dark/light × four characters — muted, vivid, soft and crisp.

Every palette clears a WCAG contrast ratio against its own background: 4.5:1 for body text, 7:1 on crisp, never below 3:1 for any token. Writing your own is a plain object; the shipped set's construction rules are in src/themes/palettes.const.ts's header.

The monospace font on React Native

Every shipped palette says fontFamily: 'monospace'. That is correct on the web and on Android, and wrong on iOS: monospace is an Android family name, iOS finds no font by it, and it falls back to the proportional system font without warning — which stays invisible until something has to line up. Nothing here can call Platform.select, because importing a platform is the one thing this design rules out, so override the token:

import { Platform } from 'react-native';
import { DARK_THEME } from '@axonpack/react-pretty-print/themes';

const MONOSPACE = Platform.select({ ios: 'Menlo', default: 'monospace' });
const theme = { ...DARK_THEME, fontFamily: MONOSPACE };

It has to be a single family name, not a CSS stack: React Native looks the string up verbatim, so "Menlo, monospace" matches nothing on either platform. Menlo ships with every iOS. example-native/fonts.ts is this, and nothing on the DOM needs it.

Languages

38, exported as SUPPORTED_LANGUAGES.

| Group | Languages | format re-indents | | --------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------- | | Braces, //, /* */ | javascript typescript tsx java kotlin swift go rust c cpp csharp php dart scala protobuf | yes | | # comments | python ruby bash dockerfile makefile graphql | graphql only | | Key/value | json json5 yaml toml ini properties | json and json5 | | Angle brackets | html xml svg markdown | no | | Stylesheets | css scss less | yes | | Line-oriented | sql diff log | sql only | | Unstyled | plain | no |

Indentation-sensitive and line-oriented formats are excluded from format deliberately: python and yaml carry meaning in the whitespace they already have, markup and markdown have their own shape, and a diff or a log is already one record per line.

Utilities

Everything the renderers use is exported, so a caller that wants its own rendering can take the parsed output and skip the components.

| Export | Signature | Notes | | ------------------------ | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | | detectLanguage | (mimeType: string \| undefined, body: string) => Language | Reads the content type, then sniffs the body. Use it when a server labels everything text/plain. | | tokenize | (code: string, language: Language) => Token[] | { type, text } per token; concatenating text returns the input unchanged. This is the renderer escape hatch. | | formatCode | (code: string, language: Language) => string | Re-indents, or returns the input for a language it does not format. | | SUPPORTED_LANGUAGES | Language[] | Sorted. | | MAX_HIGHLIGHT_LENGTH | 50_000 | Above this CodeHighlight renders unhighlighted rather than blocking. | | parseXml | (source: string) => { root: XmlElement } \| { error: string } | Never throws. | | isExpandable | (value: JsonValue) => boolean | True for any object or array. | | hasChildren | (value: JsonValue) => boolean | False for {} and [], which is why they get no toggle. | | isPlainObject | (value: JsonValue) => boolean | null is not one. | | buildPreview | (value: object \| array) => string | The summary a closed node shows. | | chunkArrayRange | (length: number) => [number, number][] | The bucket boundaries for a long array. | | collectExpandablePaths | (path: string, value: JsonValue) => string[] | Every path under a node, for expanding a subtree. | | formatCopyValue | (value: JsonValue) => string | JSON.stringify at two spaces. | | ARRAY_CHUNK_SIZE | 10 | Arrays longer than this are bucketed. |

Types

JsonValue, Language, Token, TokenType, MenuItem, Matcher, MatchRange, SearchModes, SearchQuery, TextSegment, Primitives, JsonTreeProps, XmlTreeProps, CodeHighlightProps, XmlNode, XmlElement, XmlText, XmlCData, XmlParseResult from the root; PrettyPrintTheme from /themes.

TokenType is one of keyword, string, comment, number, function, tag, attr-name, attr-value, property, selector, punctuation, plain.

What's built, and what isn't

The notes — one per area, each owning its own feature list, what is not built yet, and what the platforms make impossible, along with the design decisions behind it: Primitives · JSON · XML · Code · Themes.

License

MIT © Md Asadujjaman.