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

@graph-render/tournament-tree

v1.5.5

Published

<p> <a href="https://www.npmjs.com/package/@graph-render/tournament-tree"><img src="https://img.shields.io/npm/v/@graph-render/tournament-tree" alt="npm version" /></a> <a href="https://github.com/graph-render/graph-render/blob/main/LICENSE"><img src=

Readme

@graph-render/tournament-tree

A complete tournament bracket component for React.

Drop in <TournamentBracket>, pass your match data, and get a fully interactive bracket — with match cards, scores, winners, round labels, stage navigation, dark mode, mobile-friendly zoom, and SVG export — all styled and ready to use.

Install

yarn add @graph-render/tournament-tree react react-dom

Quick Start

import { MatchStatus, TournamentBracket } from '@graph-render/tournament-tree';

const graph = {
  nodes: {
    sf1: {
      meta: {
        players: [
          { name: 'Paul Coll', seed: 1 },
          { name: 'Mohamed ElShorbagy', seed: 4 },
        ],
        sets: [
          [11, 9],
          [9, 11],
          [11, 7],
        ],
        status: MatchStatus.Completed,
      },
    },
    sf2: {
      meta: {
        players: [
          { name: 'Ali Farag', seed: 2 },
          { name: 'Tarek Momen', seed: 3 },
        ],
        sets: [
          [11, 8],
          [11, 6],
        ],
        status: MatchStatus.Completed,
      },
    },
    final: {
      meta: {
        players: [
          { name: 'Paul Coll', seed: 1 },
          { name: 'Ali Farag', seed: 2 },
        ],
        status: MatchStatus.Upcoming,
      },
    },
  },
  adj: {
    sf1: { final: { id: 'sf1-final', type: 'undirected' } },
    sf2: { final: { id: 'sf2-final', type: 'undirected' } },
    final: {},
  },
};

export default function App() {
  return (
    <TournamentBracket
      graph={graph}
      title="World Championship"
      defaultNavigationMode
      onMatchClick={(match) => console.log(match)}
    />
  );
}

Inputs and outputs

Inputs (what you pass in)

| Input | Type | Required | Role | | ---------------------- | --------------------------------------- | --------- | --------------------------------------------------------- | | graph | NxGraphInput | yes | Bracket structure + match metadata on each node | | graph.nodes[id].meta | SquashMatchMeta | per match | Players, scores, status, tiebreaks | | graph.adj | adjacency map | yes | Parent → child links between matches | | config | Partial<GraphConfig> | no | Layout engine: tree layout, canvas size, nodeGap, edges | | appearance | TournamentBracketAppearance | no | Visual styling: colors, fonts, card size, chrome | | Other props | see Component props | no | Title, compact mode, callbacks, viewport |

Outputs (what you get back)

| Output | Type | When | | -------------------------- | ---------------------- | ---------------------------------------------------- | | Rendered bracket UI | React tree | Always — header, stage labels, graph canvas, toolbar | | onMatchClick(node) | SquashPositionedNode | User clicks a built-in match card | | onInvalidNode(id, error) | string, Error | Custom or built-in node fails to render | | SVG export | file download | User clicks export in toolbar (built-in handler) | | Dark mode | document / toolbar | Toggles document dark class via built-in control |

For custom integrations, use useBracketAppearance() inside children of BracketAppearanceProvider, or call resolveBracketAppearance(appearance, isDarkMode, compact) to get the merged style object without rendering.


Styling & configuration

Visual styling and graph layout are separate:

  • appearance — colors, typography, match-card dimensions, header, frame, stage labels
  • config — graph layout (layout, width, height, padding, theme.nodeGap, routing, etc.)
  • compact — selects appearance.matchCard.compact vs appearance.matchCard.standard presets (default card size and density)

Every field in appearance is optional. Omitted values use library defaults.

Minimal styling example

<TournamentBracket
  graph={graph}
  compact={false}
  appearance={{
    colors: {
      light: { ICON_BG: '#2563eb', SURFACE_BG: '#f8fafc' },
      dark: { SURFACE_BG: '#0f172a', ICON_BG: '#3b82f6' },
    },
    typography: {
      bodyFontFamily: '"Inter", system-ui, sans-serif',
      scoreFontFamily: '"JetBrains Mono", monospace',
    },
  }}
/>

Full styling example

import type { TournamentBracketAppearance } from '@graph-render/tournament-tree';

const brandAppearance: TournamentBracketAppearance = {
  colors: {
    light: {
      ICON_BG: '#7c9070',
      CARD_BORDER: '#d9d6cf',
      WINNER_CREST_BG: '#7c9070',
    },
    dark: {
      SURFACE_BG: '#191e24',
      CARD_BORDER: '#5d6470',
    },
  },
  typography: {
    bodyFontFamily: '"Plus Jakarta Sans", "Segoe UI", sans-serif',
    scoreFontFamily: '"Space Mono", ui-monospace, monospace',
  },
  matchCard: {
    standard: {
      width: 280,
      height: 100,
      borderRadius: 14,
      nameFontSize: 13,
      score: {
        fontSize: 14.5,
        matchCountFontSize: 21,
        segmentWidth: 24,
        segmentGap: 10,
      },
    },
    compact: {
      width: 160,
      height: 56,
      borderRadius: 8,
      score: {
        fontSize: 7.5,
        matchCountFontSize: 11,
        segmentWidth: 9,
        segmentGap: 5,
      },
    },
  },
  frame: {
    maxWidth: 1180,
    borderRadiusStandard: 24,
    contentPaddingStandard: '12px 24px 24px',
  },
  header: {
    titleFontSizeStandard: 18,
    iconSizeStandard: 30,
  },
  stageLabels: {
    gridGapStandard: 24,
    labelFontSizeStandard: 12,
  },
};

<TournamentBracket graph={graph} appearance={brandAppearance} compact={false} />;

Graph layout (config)

Use config when you need to change how the bracket is laid out on the canvas (not how match cards look):

import { EdgeType, LayoutDirection, LayoutType } from '@graph-render/types';

<TournamentBracket
  graph={graph}
  config={{
    width: 1600,
    height: 1200,
    padding: 40,
    layout: LayoutType.Tree,
    layoutDirection: LayoutDirection.LTR,
    defaultEdgeType: EdgeType.Undirected,
    theme: { nodeGap: 34, edgeColor: '#d9d6cf' },
  }}
/>;

config.theme only affects the graph engine (edges, canvas background, node spacing). Match-card colors come from appearance.colors.

Default match-card sizes

| Mode | compact | Default size (W×H) | | -------- | --------- | ------------------ | | Standard | false | 280 × 100 | | Compact | true | 160 × 56 |

Constants exported for reference: NODE_DIMENSIONS, NODE_DIMENSIONS_COMPACT, NODE_DIMENSIONS_STAGE_NAV.


appearance reference

Type: TournamentBracketAppearance (from @graph-render/tournament-tree or @graph-render/types).

appearance.colors

Override theme tokens per color mode. Keys are merged on top of built-in light/dark palettes.

| Key | Used for | | ---------------------------------------- | ----------------------------------------------------- | | BASE_BG | Match card background | | SURFACE_BG | Outer bracket frame background | | HEADER_BG | Top header bar | | HEADER_TITLE | Title text | | HEADER_BORDER | Header / stage label dividers | | ICON_BG / ICON_FG | Trophy icon badge | | BADGE_BG / BADGE_TEXT / BADGE_DOT | Header status badge | | CREST_BG / CREST_TEXT | Player initials badge | | WINNER_CREST_BG / WINNER_CREST_TEXT | Winner initials badge | | ROW_BG / ROW_HOVER_BG | Player row backgrounds | | FOREGROUND / MUTED_TEXT | Primary / secondary text | | BORDER / DARK_BORDER / CARD_BORDER | Dividers and card outline | | LIVE_INDICATOR | Live match dot | | EDGE_COLOR | Bracket connector lines (also set via config.theme) | | LABEL_TEXT | Round label text | | TOOLBAR_* | Floating toolbar | | SHADOW / CARD_SHADOW | Frame and card shadows |

appearance: {
  colors: {
    light: { ICON_BG: '#2563eb' },
    dark: { SURFACE_BG: '#0f172a' },
  },
}

appearance.typography

| Field | Default | Description | | ----------------- | ----------------------- | --------------------- | | bodyFontFamily | Plus Jakarta Sans stack | Names, header, badges | | scoreFontFamily | Space Mono stack | Per-set score digits |

appearance.matchCard.standard / .compact

Applied when compact={false} or compact={true} respectively.

| Field | Standard default | Compact default | Description | | -------------------------- | ---------------- | --------------- | ------------------------------ | | width | 280 | 160 | Card width (px); drives layout | | height | 100 | 56 | Card height (px) | | borderRadius | 14 | 8 | Card corner radius | | insetX | 10 | 6 | Horizontal padding inside card | | badgeSize | 24 | 16 | Player crest size | | badgePad | 6 | 4 | Gap after crest | | badgeFontSize | 12 | 8 | Crest initials font size | | nameFontSize | 13 | 10 | Player name font size | | matchCountWidth | 20 | 14 | Width of “sets won” column | | matchCountTrailingGap | 8 | 6 | Space before sets-won column | | scoreGroupTrailingGap | 4 | 4 | Space before score group | | rowPadding | 8px 10px | 4px 6px | HTML row padding | | rowGap | 5 | 4 | HTML row grid gap | | score.segmentWidth | 24 | 9 | Width per set-score column | | score.segmentGap | 10 | 5 | Gap between set scores | | score.fontSize | 14.5 | 7.5 | Set score font size | | score.matchCountFontSize | 21 | 11 | Sets-won number font size |

appearance.frame

| Field | Description | | -------------------------------------------------- | ----------------------------------------------- | | maxWidth | Max width of bracket container (default 1180) | | borderRadiusStandard / borderRadiusCompact | Outer frame radius | | contentPaddingStandard / contentPaddingCompact | Padding around graph canvas | | canvasBackgroundLight / canvasBackgroundDark | CSS background behind the graph |

appearance.header

| Field | Description | | ------------------------------------------------ | ----------------------------- | | gap | Flex gap between header items | | minHeightStandard / minHeightCompact | Header bar min height | | paddingStandard / paddingCompact | Header horizontal padding | | iconSizeStandard / iconSizeCompact | Trophy icon box size | | iconRadiusStandard / iconRadiusCompact | Trophy icon corner radius | | titleFontSizeStandard / titleFontSizeCompact | Title font size | | badgeFontSizeStandard / badgeFontSizeCompact | Badge label font size | | badgePaddingStandard / badgePaddingCompact | Badge padding | | badgeDotSize | Status dot size in badge |

appearance.stageLabels

| Field | Description | | -------------------------------------------------------- | -------------------------------- | | backgroundLight / backgroundDark | Stage label bar background | | paddingStandard / paddingCompact | Padding when showing all rounds | | paddingNavigationStandard / paddingNavigationCompact | Padding in stage navigation mode | | gridGapStandard / gridGapCompact | Gap between round labels | | labelFontSizeStandard / labelFontSizeCompact | Round name font size | | activePillFontSize* / activePillPadding* | Active stage chip in nav mode | | counterFontSize* | “2/5” stage counter font size | | navColorLight / navColorDark | Prev/next arrow color | | navBorderLight / navBorderDark | Prev/next button border |


Component props

| Prop | Type | Default | Description | | ----------------------- | -------------------------------------- | ------------------------ | ----------------------------------------------- | | graph | NxGraphInput | required | Bracket nodes + edges | | config | Partial<GraphConfig> | tournament defaults | Layout, canvas, routing | | appearance | TournamentBracketAppearance | built-in theme | Visual styling overrides | | defaultViewport | Partial<GraphViewport> | auto fit | Initial pan/zoom (x, y, zoom) | | vertexComponent | VertexComponent | built-in card | Replace match card renderer | | nodeRenderMode | SquashNodeRenderMode | 'export' | 'svg' | 'html' | 'export' | 'server' | | title | string | 'Tournament Bracket' | Header title | | badgeText | string | auto from graph | Header badge text | | showToolbar | boolean | true | Show toolbar actions | | showViewportControls | boolean | false | Show zoom controls on canvas | | defaultNavigationMode | boolean | true | Start in per-stage navigation | | panEnabled | boolean | true (off in nav mode) | Allow panning | | zoomEnabled | boolean | true (off in nav mode) | Allow zoom | | pinchZoomEnabled | boolean | true (off in nav mode) | Allow pinch zoom | | compact | boolean | true | Use compact match-card preset | | onMatchClick | (node: SquashPositionedNode) => void | — | Match click handler | | onInvalidNode | (id, error) => void | — | Node render error handler |


Match data input (SquashMatchMeta)

Each node in graph.nodes can include meta:

interface SquashMatchMeta {
  players?: Array<{
    name: string;
    seed?: number;
    country?: string;
  }>;
  sets?: number[][]; // e.g. [[11, 8], [9, 11], [11, 7]]
  tiebreaks?: number[][]; // optional tiebreak per set
  status?: MatchStatus; // Completed | Live | Upcoming
  currentSet?: number; // live: index of set in progress
  stage?: string; // optional label override
}

MatchStatus

import { MatchStatus } from '@graph-render/tournament-tree';

// MatchStatus.Completed — winner highlighted
// MatchStatus.Live       — live indicator, in-progress set excluded from set count
// MatchStatus.Upcoming   — dimmed, scores hidden

Bracket shape input (graph.adj)

Connect matches through adj. Round labels are inferred from graph depth.

const graph = {
  nodes: {
    /* id -> { meta } */
  },
  adj: {
    qf1: { sf1: { id: 'qf1-sf1', type: 'undirected' } },
    sf1: { final: { id: 'sf1-final', type: 'undirected' } },
    final: {},
  },
};

Advanced: hooks and exports

useBracketAppearance()

Read the resolved style object inside custom children (requires BracketAppearanceProvider, which TournamentBracket sets up automatically):

import { useBracketAppearance } from '@graph-render/tournament-tree';

function MyOverlay() {
  const { colors, matchCard, typography } = useBracketAppearance();
  return <div style={{ color: colors.HEADER_TITLE }}>...</div>;
}

resolveBracketAppearance(appearance, isDarkMode, compact)

Merge user appearance with defaults without rendering — useful for Storybook or tests.

Re-exports

| Export | Description | | ------------------------------------------------------------------------- | -------------------------------- | | TournamentBracket | Main component | | SquashNode | Standalone match card | | BracketAppearanceProvider, useBracketAppearance | Appearance context | | resolveBracketAppearance | Merge appearance with defaults | | TournamentBracketAppearance, ResolvedBracketAppearance, … | Types | | NODE_DIMENSIONS, NODE_DIMENSIONS_COMPACT, NODE_DIMENSIONS_STAGE_NAV | Default sizes | | DEFAULT_TOURNAMENT_CONFIG, COMPACT_TOURNAMENT_CONFIG, … | Default config presets | | MatchStatus, SquashNodeRenderMode, VerticalStagePosition | Enums | | getStageViewport, buildStageViews, roundLabelsForGraph | Utilities |


Custom match card

Replace the built-in card with your own renderer (styling is then your responsibility unless you read useBracketAppearance()):

import { TournamentBracket } from '@graph-render/tournament-tree';
import type { SquashMatchMeta, VertexComponentProps } from '@graph-render/types';

function MyMatchCard({ node }: VertexComponentProps) {
  const meta = node.meta as SquashMatchMeta;
  return (
    <foreignObject width={node.size?.width} height={node.size?.height}>
      <div className="my-match-card">
        {meta.players?.[0]?.name} vs {meta.players?.[1]?.name}
      </div>
    </foreignObject>
  );
}

<TournamentBracket graph={graph} vertexComponent={MyMatchCard} />;

License

MIT — free for personal and commercial use. See LICENSE.