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 🙏

© 2024 – Pkg Stats / Ryan Hefner

vanilla-extract-react-bake

v0.5.0

Published

A variant-focused typesafe component factory for vanilla extract recipes

Downloads

4

Readme

vanilla-extract-react-bake

Heavily inspired by styled from stitches, this package provides a bake function that can be used to generate typed components that reflect a vanilla-extract recipe, where variants are props.

Installation

npm install --save vanilla-extract-react-bake

Usage

Create a recipe using @vanilla-extract/recipes.

import { recipe } from '@vanilla-extract/recipes';

export const myRecipe = recipe({
  base: {
    color: 'gray'
  },

  variants: {
    size: {
      small: {
        fontSize: 12
      },
      medium: {
        fontSize: 16
      },
      large: {
        fontSize: 20
      }
    }
  }
});

Create a component using bake with your recipe.

import { bake } from 'vanilla-extract-react-bake';
import { myRecipe } from './myRecipe.css';

export const MyComponent = bake('div', myRecipe);

Use that component with props that match the variants in your recipe.

import { MyComponent } from './MyComponent';

export const MyApp = () => {
  return (
    <MyComponent size="large">Hello world</MyComponent>
  );
};

Features

Simple classname components

Since we're already piped to create components with bake, we can also use it to create simple components that just have a classname.

const MyComponent1 = bake('div');
const MyComponent2 = bake('div', 'some-classname');
const MyComponent3 = bake('button', [
  'my',
  'list',
  'of',
  'classnames'
]);

Regular vanilla extract

Since it works with regular classname, that means we can also use it for regular vanilla extract imports.

import { myStyles } from './myStyles.css';

const MyComponent = bake('div', myStyles);

Required variants

I'd love for there to be a way to infer whether a variant is required or not, but that doesn't seem possible with the current types in vanilla-extract. So instead, you can mark a variant as required in the config, which is the optional third argument.

const MyComponent = bake('div', basic, {
  required: ['requiredVariantName']
});

Variant prop passthrough

By default, we pull out the variants from the props before we spread them onto the resulting component. In the vast majority of cases, this is what you'd want, because there is no html attribute that has the same name as your variant, and you'd make react mad at you.

However, in some cases, you may want to name one of your variants after an html attribute. This tends to happen with disabled or required or something like that on form elements. For these cases you can use the forward config option:

const MyComponent = bake('div', basic, {
  forward: ['disabled']
});

This will both trigger the disabled variant, and pass the disabled prop through to the resulting component.

as Runtime base component override

In some cases you want to override the underlying that was extended at runtime. It's not suggested to make large shifts in the base component type, but usually just provide a more specific version of the same thing. Though it also pays off in some cases where you're abstracting over something like a link and button behind the scenes.

Honestly I don't know how strong the types are when you do this. I'm not smart enough yet to understand how that would work.

You can pass a string

const MyComponent = bake('div', basic);

export const MyApp = () => {
  return <MyComponent as="span">Hello world</MyComponent>;
};

Or you can pass another component

import { Link as NextLink } from 'next/link';
import { Link } from './myBakedLink';

export const MyApp = () => {
  return (
    <Link as={NextLink} href="/some-page">
      Some page
    </Link>
  );
};

Classname merging

The resulting components from bake can be used with other classnames, and the resulting classnames will be merged together.

const MyComponent = bake('div', 'some-classname');

export const MyApp = () => {
  return <MyComponent className="another-classname" />;
};

The resulting html will append them together in the order of the bake classname, and then the inlined classname.

Advanced features that are probably buggy

inject

This feature allows you to inject a prop builder for a custom prop on the resulting component. This is useful for entirely different reasons than most of the rest of the code, but it's nearly free to add to this specific code, so I thought I'd give it a shot.

It's useful when you want your component to have some sort of custom prop, and the value passed to that prop results in values being set on other non-custom props.

const MyComponent = bake('div', basic, {
  inject: {
    // This adds a 'css' prop that just passes the
    // value through to the style attribute
    css: (val: React.CSSProperties) => {
      return { style: val };
    }
  }
});

export const MyApp = () => {
  // the `css` prop here is typed as React.CSSProperties
  // from the declaration above in the inject config
  return <MyComponent css={{ color: '#BADA55' }} />;
};

This particular example of just 'renaming' the style attribute to css isn't interesting, but you could imagine that css takes in the values from your css-in-js library of choice, and then outputs the actual style values to the style attribute, or emits a class to className.

NOTE: there's a lot of nuance in the order that injected props are executed and merged together. They also don't have any other introspection into the component values at the moment (e.g. you can't read the value of children and use it to build your result).

makeBake

I think inject is quite powerful when you can use it across your entire design system. However, it's a bit of a bummer to need to pass the same inject config to every single bake call. So I've added a makeBake function that allows you to create a bake function that has the inject config already applied.

import { makeBake } from 'vanilla-extract-react-bake';

const myBake = makeBake({
  inject: {
    // This adds a 'css' prop that just passes the
    // value through to the style attribute
    css: (val: React.CSSProperties) => {
      return { style: val };
    }
  }
});

// myBake is now a function that can be used to create components
// just like `bake`, but the `css` injection we did above will
// always be applied to all resulting components
const MyComponent = myBake('div', basic);

export const MyApp = () => {
  // the `css` prop here is typed as React.CSSProperties
  // from the declaration above in the inject config
  return <MyComponent css={{ color: '#BADA55' }} />;
};

Why is this good?

The code for actually building the components is relatively straightforward, and is only a few lines of code. The primary benefit here is the type safety that comes with it. Much like with vanilla-extract directly, a large amount of your code is built out at compile time. So you ship less javascript to the client, you have types that never drift from your styles, and you have to write less code by hand.

It's primarily good for folks who spend a lot of time reimplementing their style variants as javascript props on their components, a common job for people making design systems, or just reusable components in general. bake allows you to have a single place where your variants are defined, and then outputs a very well-typed component that can be used with those variants, and updates when the variants change.

It's especially apparent when you look at the intellisense for components that come out of bake.

A basic recipe with one variant

A basic recipe with color variant and values: red and blue

The variants are now known, and the config can be used to mark some of them as required

Typescript intellisense showing a red line under baked component

The error describes the specific prop that is missing

Typescript intellisense showing the missing required prop 'color'

IntelliSense for the component itself shows the variants and their values

Typescript intellisense showing the variants

Typescript intellisense showing the values of variants