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

@bento/slots

v0.3.0

Published

The slots implementation of Bento

Readme

Slots

The @bento/slots package provides a way to customize components using slots. Allowing developers full control over every part of the component. It automatically intercepts the slot and slots props of the components and introduces the requested changes.

This package should be used in conjunction with the @bento/use-props package.

Installation

npm install --save @bento/slots @bento/use-props

withSlots

The withSlots function is used to create a new component that supports slots.

import { useProps } from '@bento/use-props';
import { withSlots } from '@bento/slots';

export const Button = withSlots('BentoButton', function BentoButton(args) {
  const { props, apply } = useProps(args);

  // Do your magic ✨
});

The withSlots function accepts three arguments:

The name argument does not need to match the name of under which you export your component but it is recommended to use the same name to avoid confusion. The reason why we require the name to be unique is because it's used to identify the component. This allows the component to be targeted with a global slot override, but it will also be used as displayName to ensure a readable component name in the React DevTools.

Data Attributes

When a slot prop is added to a slottable component, a data-slot attribute is automatically added to the rendered DOM element. This allows developers to target specific slots using CSS selectors or for debugging purposes.

<Button slot="submit">Submit</Button>
// Renders: <button data-slot="submit">Submit</button>

This is particularly useful for:

  • CSS Targeting: Style specific slots without additional classes.
  • Debugging: Quickly identify which slot a component belongs to in DevTools.
  • Testing: Select elements by their slot name in tests.
/* Target all elements with a slot name "submit" */
[data-slot="submit"] {
  background-color: blue;
}

/* Note: Each component only receives its direct slot prop value as data-slot,
   not a concatenated namespace. For example, if you have nested slots like
   "form.submit", the submit button will have data-slot="submit", not data-slot="form.submit". */

/* Target nested slots using attribute selectors */
[data-slot="form"] [data-slot="submit"] {
  margin-top: 1rem;
}

Forwarding Refs

Components created with withSlots automatically support ref forwarding. The withSlots function:

  1. Detects if your component accepts a ref parameter (by checking if it has 2 parameters)
  2. Automatically wraps it with React.forwardRef in React 18 (if not already wrapped)
  3. In React 19+, it relies on the native ref-as-prop behavior (no wrapping needed)

When using useProps, you can access the merged ref that combines:

  • The ref passed directly to the component
  • Any refs supplied through slots
  • The forwarded ref from parent components
import { useProps } from '@bento/use-props';
import { withSlots } from '@bento/slots';
import React from 'react';

export const Button = withSlots(
  'BentoButton',
  React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(function BentoButton(args, forwardedRef) {
    const { props, apply, ref } = useProps(args, {}, forwardedRef);

    return <button {...apply({}, ['ref'])} ref={ref}>{ props.children }</button>;
  })
);

The ForwardRefExample demonstrates how refs coming from the top-level consumer and from slot overrides are merged into a single handle.

React 19 Compatibility

The ref forwarding implementation is designed to be React 19-compatible:

  • React 18: Components with 2 parameters are automatically wrapped with forwardRef
  • React 19: Components use the native ref-as-prop behavior, no wrapping needed

This means your components will work seamlessly when upgrading from React 18 to React 19 without any code changes.

Modifiers

Modifiers are functions that are ran when the supplied component is rendered. They can be used to modify the component or received props. This allows common modifications to be shared between components.

Each modifier is a function that takes an object with the following properties:

The modification happen based on the return value of the modifier. If nothing is returned, the component will be rendered as is. If you want to introduce a new Component to be rendered, you can return an object with a Component property. If you want to modify or introduce props that are passed to the component, you can return an object with a props property. The same applies to context.

For the props and contex the returned object is merged with the existing values of their respective properties. So you don't need to merge the existing values yourself. The Component is always replaced by the returned Component.

In the example below, the modifier function introduces a new prop to the component:

export function modifier({ Component, context, props }) {
  return {
    props: {
      newProp: 'new value'
    }
  }
}

This modifier should then be passed in the third argument to withSlots:

import { replaces } from '@bento/slots/modifiers/replaces';
import { override } from '@bento/slots/modifiers/override';
import { withSlots } from '@bento/slots';
import { modifier } from './modifier';

const Button = withSlots('MyButton', function MyButton(props) {
  return <button {...props} />;
}, [override, replaces, modifier]);

// Input: <Button>Hello</Button>
// Output: <button newProp="new value">Hello</button>

If you don't want any modifiers to be applied to your component, you can supply an empty array as the third argument to withSlots:

import { withSlots } from '@bento/slots';

export const Example = withSlots('Example', function Example(props) {
  return (
    <div {...props}>
      <Header slot="header" />
      <Body slot="content" />
      <Footer slot="footer" />
    </div>
  );
}, [/* Supply an empty array if you don't want to use the modifers */]);

The following modifiers are applied to the created component by default:

override

The override modifier is used to introduce a data-override prop to the component when it detects that certain overrides are applied to the component. This attribute makes it easier to determine where the difference between the original Component and the current rendered component are originating from.

For you as a developer, this means you can easily see which components can be easily upgrade to the new version without any problems, and which components would require some additional attention and which areas specifically.

The data-override attribute is a space-separated list of the names that indicates the following modifications:

  • style: Custom styling has been applied to the component using the `style prop.
  • className: Custom className has been applied to the component using the className prop.
  • slot: The component has been modified using slots. When the slots make changes to style or className, these are also included in the resulting data-override attribute.
  • context: The whole component or parent component has been modified using the replaces modifier and a different component has been rendered in its place.
import { override } from '@bento/slots/modifiers/override';

replaces

The replaces modifier is used to introduce "global" override of components in your application. The slot functionality that this package provides is great for making small changes to a few components, but there might be use-case where you need to make changes to every instance of a Component, e.g., in the case of experimentation.

import { replaces } from '@bento/slots/modifiers/replaces';

Slot Merging

Slot merging allows slots to be progressively enhanced as they flow through a component tree. When multiple components define slots for the same slot name, these slots are merged together.

Object Slot Merging

When both parent and child define object slots (props), they are merged with parent taking precedence:

// Child defines props
<Component slots={{ label: { className: 'child', id: 'child-id' } }} />

// Parent adds more props
<Child slots={{ label: { className: 'parent', title: 'parent-title' } }} />

// Result: { className: 'parent', id: 'child-id', title: 'parent-title' }

Function Slot Merging

When function slots are merged, the parent function becomes active but receives access to all previous implementations via the previous parameter:

import { MergedFunction } from './examples/merged-function.tsx';

// Each enhancement level adds its own function
const FirstEnhancement = () => (
  <BaseComponent slots={{
    container: function firstWrapper() {
      return <div>First Enhancement</div>;
    }
  }} />
);

const SecondEnhancement = () => (
  <FirstEnhancement slots={{
    container: function secondWrapper() {
      return <div>Second Enhancement</div>;
    }
  }} />
);

// Final component can access all previous functions
<ThirdEnhancement slots={{
  container: function wrapper({ previous }) {
    return (
      <div>
        {previous[0]()}  {/* First Enhancement */}
        {previous[1]()}  {/* Second Enhancement */}
        {previous[2]()}  {/* Third Enhancement */}
      </div>
    );
  }
}} />

Function Slot Parameters

Function slots receive an object with:

  • props: The component's props
  • original: The original React element
  • previous: Array of previous slot implementations (child → parent order)

Key Rules

  • Object slots: Parent props override child props for same keys
  • Function slots: Parent function is called, previous functions available in previous array
  • Mixed types: Function slots take precedence over object slots
  • Previous array: Ordered from closest child to furthest ancestor

Validation

Compositional components often depend on specific child slots to function correctly. For example, an Overlay component needs content to display, or a Dialog component requires both a title and body. Without validation, missing required slots would cause runtime errors or silent failures that are difficult to debug.

contains

The contains utility validates that required slot assignments are present in children. This is useful for compositional components that depend on specific child slots to function correctly.

import { contains } from '@bento/slots';
import { BentoError } from '@bento/error';
import { Container } from '@bento/container';

export const Dialog = withSlots('Dialog', function Dialog(args) {
  const { children } = args;

  // Validate required slots are present
  if (!contains(['title', 'content'], children)) {
    throw new BentoError({
      name: 'dialog',
      method: 'Dialog',
      message: 'Dialog requires children with slot="title" and slot="content"'
    });
  }

  return <Container>{children}</Container>;
});

The contains function recursively searches through React children to find components with matching slot props. It only validates components wrapped with withSlots() - raw HTML elements with slot props are ignored since they are not part of the Bento slot system.

Namespaced Slot Validation

You can validate deeply nested slot structures using dot notation. When searching for 'submit.icon', contains will look for a component with slot="submit" that has a child component with slot="icon":

import { Button } from '@bento/button';
import { Icon } from '@bento/icon';
import { BentoError } from '@bento/error';

// Check for a nested slot path
if (!contains(['submit.icon'], children)) {
  throw new BentoError({
    name: 'form',
    method: 'Form',
    message: 'Submit button must have an icon'
  });
}

// This will find:
// <Button slot="submit">
//   <Icon slot="icon">→</Icon>
// </Button>