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 🙏

© 2025 – Pkg Stats / Ryan Hefner

react-bracket-ui

v1.2.0

Published

A modern, feature-rich React component library for displaying single-elimination tournament brackets with drag-drop, zoom/pan, and error validation

Readme

react-bracket-ui

✨ Features

  • Pure Display Component - Only displays bracket data passed from FE
  • React 19 Ready - Built with TypeScript and latest React features
  • Zoom & Pan - Interactive zoom/pan for better bracket navigation
  • Drag & Drop - Rearrange teams between matches with intuitive drag-and-drop
  • Error Validation - Visual error indicators (red borders) for invalid matches
  • Callbacks - Emit events when users modify bracket positions
  • Customizable - Extensive styling and color customization options
  • Optimized - Performance-optimized with React.memo and useMemo
  • TypeScript - Full type safety with comprehensive TypeScript definitions
  • Zero Config - Works out-of-the-box with sensible defaults

📦 Installation

npm install react-bracket-ui
# or
yarn add react-bracket-ui
# or
pnpm add react-bracket-ui

🚀 Quick Start

import React from 'react';
import { Bracket } from 'react-bracket-ui';
import type { Match } from 'react-bracket-ui';

const matches: Match[] = [
  {
    id: 1,
    round: 1,
    participant1: { id: 'p1', name: 'Team A', score: 2 },
    participant2: { id: 'p2', name: 'Team B', score: 1 },
    winner: 'p1',
    nextMatchId: 3
  },
  {
    id: 2,
    round: 1,
    participant1: { id: 'p3', name: 'Team C', score: 3 },
    participant2: { id: 'p4', name: 'Team D', score: 0 },
    winner: 'p3',
    nextMatchId: 3
  },
  {
    id: 3,
    round: 2,
    participant1: { id: 'p1', name: 'Team A' },
    participant2: { id: 'p3', name: 'Team C' }
  }
];

function App() {
  return (
    <Bracket 
      matches={matches}
      showRoundNames={true}
    />
  );
}

📖 API Reference

BracketProps

| Prop | Type | Default | Description | |------|------|---------|-------------| | matches | Match[] | required | Array of match objects | | className | string | undefined | CSS class name | | style | React.CSSProperties | undefined | Inline styles | | enableDragDrop | boolean | false | Enable drag & drop functionality | | onBracketChange | (event: BracketChangeEvent) => void | undefined | Callback when bracket changes | | enableZoomPan | boolean | false | Enable zoom/pan controls | | minZoom | number | 0.5 | Minimum zoom level | | maxZoom | number | 3 | Maximum zoom level | | initialZoom | number | 1 | Initial zoom level | | errors | BracketError[] | undefined | Custom validation errors | | onErrorClick | (error: BracketError) => void | undefined | Callback when error is clicked | | showRoundNames | boolean | true | Show round names (Final, Semi-Final, etc.) | | roundNames | Record<number, string> | undefined | Custom round names | | matchWidth | number | 220 | Width of match box (px) | | matchHeight | number | 100 | Height of match box (px) | | gap | number | 20 | Gap between rounds (px) | | colors | ColorConfig | default theme | Custom color scheme |

Match Interface

interface Match {
  id: string | number;
  participant1?: Participant | null;
  participant2?: Participant | null;
  winner?: string | number | null;
  round: number;
  matchNumber?: number;
  nextMatchId?: string | number | null;
  hasError?: boolean;
  errorMessage?: string;
}

Participant Interface

interface Participant {
  id: string | number;
  name: string;
  score?: number;
  seed?: number;
}

🎨 Advanced Examples

Example 1: Basic Bracket

import { Bracket } from 'react-bracket-ui';

function BasicBracket() {
  const matches = [
    {
      id: 1,
      round: 1,
      participant1: { id: 'p1', name: 'Player 1', score: 3 },
      participant2: { id: 'p2', name: 'Player 2', score: 1 },
      winner: 'p1'
    }
  ];

  return <Bracket matches={matches} />;
}

Example 2: With Drag & Drop

import { Bracket, BracketChangeEvent } from 'react-bracket-ui';
import { useState } from 'react';

function DraggableBracket() {
  const [matches, setMatches] = useState([
    {
      id: 1,
      round: 1,
      participant1: { id: 'p1', name: 'Team A' },
      participant2: { id: 'p2', name: 'Team B' }
    },
    {
      id: 2,
      round: 1,
      participant1: { id: 'p3', name: 'Team C' },
      participant2: { id: 'p4', name: 'Team D' }
    }
  ]);

  const handleBracketChange = (event: BracketChangeEvent) => {
    console.log('Bracket changed:', event);
    setMatches(event.matches);
    
    // Send to backend
    // await api.updateBracket(event.matches);
  };

  return (
    <Bracket 
      matches={matches}
      enableDragDrop={true}
      onBracketChange={handleBracketChange}
    />
  );
}

Example 3: With Zoom & Pan

import { Bracket } from 'react-bracket-ui';

function ZoomableBracket() {
  return (
    <Bracket 
      matches={matches}
      enableZoomPan={true}
      minZoom={0.3}
      maxZoom={5}
      initialZoom={1}
      style={{ height: '800px' }}
    />
  );
}

Example 4: Custom Colors & Styling

import { Bracket } from 'react-bracket-ui';

function CustomBracket() {
  return (
    <Bracket 
      matches={matches}
      colors={{
        primary: '#ff6b6b',
        secondary: '#4ecdc4',
        background: '#1a1a2e',
        error: '#ff0000',
        warning: '#ffa500',
        winner: '#90ee90'
      }}
      matchWidth={250}
      matchHeight={120}
      gap={30}
      style={{
        backgroundColor: '#0f0f23',
        border: '2px solid #4ecdc4'
      }}
    />
  );
}

Example 5: With Error Validation

import { Bracket, BracketError, validateBracket } from 'react-bracket-ui';
import { useMemo } from 'react';

function ValidatedBracket() {
  const matches = [
    {
      id: 1,
      round: 1,
      participant1: { id: 'p1', name: 'Team A' },
      participant2: { id: 'p2', name: 'Team B' },
      winner: 'p999' // Invalid winner ID!
    }
  ];

  // Automatic validation
  const errors = useMemo(() => validateBracket(matches), [matches]);

  // Or custom validation
  const customErrors: BracketError[] = [
    {
      matchId: 1,
      message: 'This match has scheduling conflict',
      type: 'warning'
    }
  ];

  return (
    <Bracket 
      matches={matches}
      errors={[...errors, ...customErrors]}
      onErrorClick={(error) => {
        alert(`Error in match ${error.matchId}: ${error.message}`);
      }}
    />
  );
}

Example 6: Custom Round Names

import { Bracket } from 'react-bracket-ui';

function CustomRoundNames() {
  return (
    <Bracket 
      matches={matches}
      showRoundNames={true}
      roundNames={{
        1: 'Round of 16',
        2: 'Quarter Finals',
        3: 'Semi Finals',
        4: 'Grand Final'
      }}
    />
  );
}

Example 7: Complete Tournament Example

import { Bracket, BracketChangeEvent } from 'react-bracket-ui';
import { useState, useCallback } from 'react';

function TournamentBracket() {
  const [matches, setMatches] = useState([
    // Round 1
    {
      id: 1,
      round: 1,
      matchNumber: 1,
      participant1: { id: 't1', name: 'Team Alpha', seed: 1, score: 3 },
      participant2: { id: 't8', name: 'Team Hotel', seed: 8, score: 0 },
      winner: 't1',
      nextMatchId: 5
    },
    {
      id: 2,
      round: 1,
      matchNumber: 2,
      participant1: { id: 't4', name: 'Team Delta', seed: 4, score: 2 },
      participant2: { id: 't5', name: 'Team Echo', seed: 5, score: 1 },
      winner: 't4',
      nextMatchId: 5
    },
    {
      id: 3,
      round: 1,
      matchNumber: 3,
      participant1: { id: 't2', name: 'Team Bravo', seed: 2, score: 3 },
      participant2: { id: 't7', name: 'Team Golf', seed: 7, score: 2 },
      winner: 't2',
      nextMatchId: 6
    },
    {
      id: 4,
      round: 1,
      matchNumber: 4,
      participant1: { id: 't3', name: 'Team Charlie', seed: 3, score: 1 },
      participant2: { id: 't6', name: 'Team Foxtrot', seed: 6, score: 3 },
      winner: 't6',
      nextMatchId: 6
    },
    // Semi-Finals
    {
      id: 5,
      round: 2,
      matchNumber: 5,
      participant1: { id: 't1', name: 'Team Alpha' },
      participant2: { id: 't4', name: 'Team Delta' },
      nextMatchId: 7
    },
    {
      id: 6,
      round: 2,
      matchNumber: 6,
      participant1: { id: 't2', name: 'Team Bravo' },
      participant2: { id: 't6', name: 'Team Foxtrot' },
      nextMatchId: 7
    },
    // Final
    {
      id: 7,
      round: 3,
      matchNumber: 7,
      participant1: null,
      participant2: null
    }
  ]);

  const handleBracketChange = useCallback(async (event: BracketChangeEvent) => {
    console.log('Bracket updated:', event);
    setMatches(event.matches);
    
    // Sync with backend
    try {
      // await updateTournamentBracket(event.matches);
      console.log('Bracket saved successfully');
    } catch (error) {
      console.error('Failed to save bracket:', error);
    }
  }, []);

  return (
    <div style={{ padding: '20px' }}>
      <h1>World Championship 2025</h1>
      
      <Bracket 
        matches={matches}
        enableDragDrop={true}
        enableZoomPan={true}
        onBracketChange={handleBracketChange}
        showRoundNames={true}
        roundNames={{
          1: 'Quarter Finals',
          2: 'Semi Finals',
          3: 'Grand Final'
        }}
        colors={{
          primary: '#1976d2',
          winner: '#c8e6c9'
        }}
        matchWidth={240}
        gap={25}
        style={{ height: '700px' }}
      />
    </div>
  );
}

🛠️ Utility Functions

validateBracket

Validates bracket structure and returns errors:

import { validateBracket } from 'react-bracket-ui';

const errors = validateBracket(matches);
// Returns BracketError[] with validation issues

hasMatchError

Check if a specific match has errors:

import { hasMatchError } from 'react-bracket-ui';

const hasError = hasMatchError(matchId, errors);

getMatchErrorMessage

Get error message for a specific match:

import { getMatchErrorMessage } from 'react-bracket-ui';

const message = getMatchErrorMessage(matchId, errors);

🎯 Use Cases

  • Tournament Management Systems - Display and manage tournament brackets
  • Sports Applications - Visualize competition brackets
  • Gaming Platforms - Show esports tournament structures
  • Event Management - Organize single-elimination events
  • Admin Dashboards - Allow admins to adjust bracket arrangements

🤝 Integration Examples

With React Query

import { useQuery, useMutation } from '@tanstack/react-query';
import { Bracket, BracketChangeEvent } from 'react-bracket-ui';

function TournamentView({ tournamentId }: { tournamentId: string }) {
  const { data: matches } = useQuery({
    queryKey: ['tournament', tournamentId],
    queryFn: () => fetchTournamentMatches(tournamentId)
  });

  const mutation = useMutation({
    mutationFn: (matches: Match[]) => updateTournament(tournamentId, matches)
  });

  const handleChange = (event: BracketChangeEvent) => {
    mutation.mutate(event.matches);
  };

  return (
    <Bracket 
      matches={matches || []}
      enableDragDrop={true}
      onBracketChange={handleChange}
    />
  );
}

With Redux

import { useDispatch, useSelector } from 'react-redux';
import { Bracket } from 'react-bracket-ui';
import { updateBracketAction } from './store/tournamentSlice';

function ReduxBracket() {
  const dispatch = useDispatch();
  const matches = useSelector(state => state.tournament.matches);

  return (
    <Bracket 
      matches={matches}
      enableDragDrop={true}
      onBracketChange={(event) => {
        dispatch(updateBracketAction(event.matches));
      }}
    />
  );
}

📋 TypeScript Support

Full TypeScript support with comprehensive type definitions:

import type { 
  Match, 
  Participant, 
  BracketProps, 
  BracketError,
  BracketChangeEvent,
  DragDropResult 
} from 'react-bracket-ui';

🔧 Troubleshooting

Drag and Drop not working

Make sure to:

  1. Set enableDragDrop={true}
  2. Provide onBracketChange callback
  3. Update your state with new matches from the callback

Zoom/Pan not working

Ensure:

  1. Set enableZoomPan={true}
  2. Provide adequate height in style prop
  3. Check if parent container has overflow issues

Errors not showing

Verify:

  1. Matches have valid IDs
  2. Using validateBracket() or providing errors prop
  3. Check console for validation messages

🌟 Performance Tips

  1. Memoize matches array - Prevent unnecessary re-renders
  2. Use stable IDs - Don't use array indices as match IDs
  3. Optimize callbacks - Wrap callbacks with useCallback
  4. Lazy load - Load large brackets progressively

📄 License

ISC License - see LICENSE file for details

👥 Author

thuatdt137

🔗 Links

🙏 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.


A React component library for displaying tournament brackets with TypeScript support.

Installation

npm install react-bracket-ui

Usage

import React from 'react';
import { Bracket, Match } from 'react-bracket-ui';

const matches: Match[] = [
  {
    id: 1,
    participant1: { id: 1, name: "Player A", score: 3 },
    participant2: { id: 2, name: "Player B", score: 1 },
    winner: 1,
    round: 1,
    nextMatchId: 3
  },
  {
    id: 2,
    participant1: { id: 3, name: "Player C", score: 2 },
    participant2: { id: 4, name: "Player D", score: 4 },
    winner: 4,
    round: 1,
    nextMatchId: 3
  },
  {
    id: 3,
    participant1: { id: 1, name: "Player A" },
    participant2: { id: 4, name: "Player D" },
    round: 2
  }
];

function App() {
  return (
    <div>
      <h1>Tournament</h1>
      <Bracket matches={matches} />
    </div>
  );
}

Props

Bracket Component

| Prop | Type | Default | Description | |------|------|---------|-------------| | matches | Match[] | required | Array of match objects | | className | string | undefined | CSS class name | | style | React.CSSProperties | undefined | Inline styles |

Match Interface

interface Match {
  id: string | number;
  participant1?: {
    id: string | number;
    name: string;
    score?: number;
  };
  participant2?: {
    id: string | number;
    name: string;
    score?: number;
  };
  winner?: string | number;
  round: number;
  nextMatchId?: string | number;
}

Features

  • ✅ TypeScript support
  • ✅ Responsive design
  • ✅ Customizable styling
  • ✅ Multiple rounds support
  • ✅ Winner highlighting
  • ✅ Score display
  • ✅ ESM and CommonJS support

License

ISC

Author

thuatdt137