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-matchings

v0.0.7

Published

A React component for matching questions and answers

Readme

react-matchings

A React component for interactive question-answer matching. Create engaging quiz and assessment interfaces where users can drag and drop connections between questions and answers.

Features

  • 🎯 Drag and Drop Interface - Intuitive drag-and-drop matching experience
  • 🎨 Highly Customizable - Customize colors, styling, and behavior through props
  • Accessible - Built with accessibility in mind
  • 🎭 Dynamic Styling - Apply conditional styles based on component state
  • 📱 Responsive - Works across different screen sizes
  • 🎪 Interactive Feedback - Visual feedback during dragging and matching

Installation

npm install react-matchings
yarn add react-matchings
pnpm add react-matchings

Basic Usage

First, import the component, its types, and CSS:

import { Matching, type TMatch } from "react-matchings";
import "react-matchings/dist/index.css";

function App() {
  const questions = [
    { id: 1, text: "What is React?" },
    { id: 2, text: "What is TypeScript?" },
    { id: 3, text: "What is JavaScript?" },
  ];

  const answers = [
    { id: 1, text: "A JavaScript library for building UIs" },
    { id: 2, text: "A typed superset of JavaScript" },
    { id: 3, text: "A programming language" },
  ];

  const handleMatchChange = (matches: TMatch[]): void => {
    console.log("Current matches:", matches);
    // matches format: [{ questionId: 1, answerId: 1 }, ...]
  };

  return (
    <Matching
      questions={questions}
      answers={answers}
      onChange={handleMatchChange}
    />
  );
}

Props

Required Props

questions

Array of question objects to display on the left side.

Type: { id: number; text: string }[]

Example:

const questions = [
  { id: 1, text: "Capital of France?" },
  { id: 2, text: "Capital of Japan?" },
  { id: 3, text: "Capital of Australia?" },
];

answers

Array of answer objects to display on the right side.

Type: { id: number; text: string }[]

Example:

const answers = [
  { id: 1, text: "Paris" },
  { id: 2, text: "Tokyo" },
  { id: 3, text: "Canberra" },
];

Note: The number of questions and answers don't need to match. Users can connect any question to any answer.


Optional Props

onChange

Callback function that is called whenever the matches change.

Type: (matches: TMatch[]) => void

Default: undefined

Note: Import TMatch type from the package: import { type TMatch } from "react-matchings";

Example:

import { Matching, type TMatch } from "react-matchings";

const handleChange = (matches: TMatch[]): void => {
  // matches is an array of connections
  // Example: [{ questionId: 1, answerId: 2 }, { questionId: 2, answerId: 1 }]
  console.log("Matches updated:", matches);
  // Save to state, send to API, etc.
};

<Matching onChange={handleChange} ... />

Note: This prop is optional. If not provided, the component will still function but won't notify parent components of changes.


className

Additional CSS classes to apply to the container element.

Type: string

Default: undefined

Example:

<Matching
  className="my-8 p-6 border border-gray-300 rounded-lg"
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

questionClassName

Custom CSS classes for question buttons.

Type: string

Default: undefined

Example:

<Matching
  questionClassName="bg-blue-500 hover:bg-blue-600 text-white font-bold"
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

answerClassName

Custom CSS classes for answer buttons.

Type: string

Default: undefined

Example:

<Matching
  answerClassName="bg-purple-500 hover:bg-purple-600 text-white"
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

lineColor

Color of the connecting lines.

Type: string

Default: "black"

Example:

<Matching
  lineColor="#3b82f6"  // blue
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

// Or use CSS color names
<Matching
  lineColor="rgb(59, 130, 246)"
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

circleColor

Color of the circles at the ends of the connecting lines.

Type: string

Default: "white"

Example:

<Matching
  circleColor="#f3f4f6" // light gray
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

circleRadius

Radius of the circles at the ends of the connecting lines, in pixels.

Type: number

Default: 8

Example:

<Matching
  circleRadius={12}
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

offset

Offset distance from the edges where lines connect, in pixels. Controls how far from the edge the connection points are.

Type: number

Default: 10

Example:

<Matching
  offset={15}
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

disabled

Whether the component is disabled. When disabled, users cannot create or remove matches.

Type: boolean

Default: false

Example:

const [isDisabled, setIsDisabled] = useState(false);

<Matching
  disabled={isDisabled}
  questions={questions}
  answers={answers}
  onChange={handleChange}
/>

<button onClick={() => setIsDisabled(!isDisabled)}>
  {isDisabled ? "Enable" : "Disable"} Matching
</button>

Complete Examples

Example 1: Basic Quiz Component

import { useState } from "react";
import { Matching, type TMatch } from "react-matchings";
import "react-matchings/dist/index.css";

function QuizApp() {
  const questions = [
    { id: 1, text: "What is the capital of France?" },
    { id: 2, text: "What is the capital of Japan?" },
    { id: 3, text: "What is the capital of Australia?" },
  ];

  const answers = [
    { id: 1, text: "Paris" },
    { id: 2, text: "Tokyo" },
    { id: 3, text: "Canberra" },
  ];

  const [matches, setMatches] = useState<TMatch[]>([]);
  const [submitted, setSubmitted] = useState<boolean>(false);

  const handleSubmit = (): void => {
    setSubmitted(true);
    // Check answers, calculate score, etc.
    console.log("Submitted matches:", matches);
  };

  return (
    <div className="p-8 max-w-4xl mx-auto">
      <h1 className="text-2xl font-bold mb-6">Geography Quiz</h1>

      <Matching
        questions={questions}
        answers={answers}
        onChange={setMatches}
        disabled={submitted}
      />

      <button
        onClick={handleSubmit}
        disabled={submitted || matches.length === 0}
        className="mt-6 px-6 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-400"
      >
        Submit Answers
      </button>
    </div>
  );
}

Example 2: Custom Styled Component

import { Matching, type TMatch } from "react-matchings";
import "react-matchings/dist/index.css";

function StyledMatching() {
  const questions = [
    { id: 1, text: "Question 1" },
    { id: 2, text: "Question 2" },
  ];

  const answers = [
    { id: 1, text: "Answer 1" },
    { id: 2, text: "Answer 2" },
  ];

  const handleChange = (matches: TMatch[]): void => {
    console.log(matches);
  };

  return (
    <Matching
      questions={questions}
      answers={answers}
      onChange={handleChange}
      className="p-8 bg-linear-to-br from-blue-50 to-purple-50 rounded-xl shadow-lg"
      questionClassName="p-4 rounded-lg font-semibold transition-all duration-200 bg-blue-500 text-white hover:bg-blue-600"
      answerClassName="p-4 rounded-lg font-semibold transition-all duration-200 bg-purple-500 text-white hover:bg-purple-600"
      lineColor="#8b5cf6"
      circleColor="#e9d5ff"
      circleRadius={10}
      offset={12}
    />
  );
}

Example 3: Assessment with Validation

import { useState, useMemo } from "react";
import { Matching, type TMatch } from "react-matchings";
import "react-matchings/dist/index.css";

function AssessmentComponent() {
  const questions = [
    { id: 1, text: "Primary color" },
    { id: 2, text: "Secondary color" },
    { id: 3, text: "Tertiary color" },
  ];

  const answers = [
    { id: 1, text: "Red, Blue, Yellow" },
    { id: 2, text: "Orange, Green, Purple" },
    { id: 3, text: "Red-Orange, Yellow-Green, Blue-Purple" },
  ];

  // Correct answers
  const correctMatches: TMatch[] = [
    { questionId: 1, answerId: 1 },
    { questionId: 2, answerId: 2 },
    { questionId: 3, answerId: 3 },
  ];

  const [matches, setMatches] = useState<TMatch[]>([]);
  const [showResults, setShowResults] = useState<boolean>(false);

  const score = useMemo(() => {
    if (!showResults) return null;
    const correct = matches.filter((match) =>
      correctMatches.some(
        (cm) =>
          cm.questionId === match.questionId && cm.answerId === match.answerId
      )
    ).length;
    return { correct, total: questions.length };
  }, [matches, showResults]);

  return (
    <div className="p-8">
      <Matching
        questions={questions}
        answers={answers}
        onChange={setMatches}
        disabled={showResults}
      />

      {showResults && score && (
        <div className="mt-6 p-4 bg-gray-100 rounded">
          <p className="text-lg font-semibold">
            Score: {score.correct} / {score.total}
          </p>
        </div>
      )}

      <button
        onClick={() => setShowResults(true)}
        disabled={showResults || matches.length < questions.length}
        className="mt-4 px-6 py-2 bg-blue-500 text-white rounded"
      >
        Check Answers
      </button>
    </div>
  );
}

How It Works

  1. Drag to Connect: Click and hold on a question, then drag to an answer and release to create a connection.

  2. Remove Connection: Click on a matched question to remove its connection.

  3. Visual Feedback:

    • Dragging shows a dashed line following your cursor
    • Matched items are visually distinguished
    • Hovering over answers while dragging highlights them
  4. State Management: The onChange callback receives an array of all current matches whenever they change.

Styling

The component uses Tailwind CSS for styling. Make sure to import the CSS file:

import "react-matchings/dist/index.css";

You can override default styles using the className props, or customize the colors using the color props.

TypeScript Support

This package includes full TypeScript definitions. Import types for type-safe usage:

import { Matching, type TMatch } from "react-matchings";

// Use the TMatch type for type safety
const handleChange = (matches: TMatch[]): void => {
  // matches is properly typed as TMatch[]
  // Each match has questionId: number and answerId: number
  matches.forEach((match) => {
    console.log(match.questionId, match.answerId);
  });
};

The TMatch type is defined as:

type TMatch = {
  questionId: number;
  answerId: number;
};

Accessibility

The component includes accessibility features:

  • Proper ARIA attributes (aria-pressed)
  • Keyboard-friendly interactions
  • Focus management
  • Semantic HTML structure

Browser Support

Works in all modern browsers that support:

  • React 18+
  • CSS Grid
  • SVG
  • Drag and Drop API

License

MIT

Contributing

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

Author

Fares Galal