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

vite-plugin-inline-style

v0.0.0

Published

Vite plugin to optimize JSX inline styling.

Readme

vite-plugin-inline-style

Vite plugin to optimize JSX inline styling.

Why, so?

Why?

We don't like TailwindCSS.

While Tailwind significantly boosts productivity, we disagree with having to study new names that simply map to what we already know well, just to reap the benefits Tailwind CSS offers. Even if they're just simple abbreviations.

So?

So we've decided to actively use inline styling. However, inline styling has several critical drawbacks:

  • Inline styles are combined with the HTML file and cannot benefit from CDN caching or browser caching.
  • Features like pseudo-classes and media queries cannot be utilized.
  • Inline style objects in JSX are recreated each time, impacting performance.

That's why we created this plugin.

It analyzes inline styles at build time and separates each inline style into some separate CSS files. This preserves the familiarity of inline styles while leveraging caching benefits and solving the issue of object recreation. And it provides utility properties that enable features like pseudo-classes and media queries.

Installation

yarn add -D vite-plugin-inline-style
// vite.config.js
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite'
import { inlineStyle } from 'vite-plugin-inline-style'

export default defineConfig({
  plugins: [
    react(), // or your favorite JSX plugin
    inlineStyle(),
  ],
})

Example

// Component.jsx
export function Component() {
  return (
    <div style={{
      width: 200,
      height: 200,
      backgroundColor: '#F00',
      color: '#FFF',
      ':hover': {
        backgroundColor: '#0F0',
      },
      '@media (min-width: 768px)': {
        width: '100%',
        ':hover': {
          backgroundColor: '#00F',
        },
      }
    }}>
      Hello, world!
    </div>
  )
}
<!-- Browser output -->
<style>
  /** Minified and extracted to a separate file on production. **/
  .s-xxxxxx {
    width: 200px;
    height: 200px;
    background-color: #F00;
    color: #FFF;
  }
  .s-xxxxxx:hover {
    background-color: #0F0;
  }
  @media (min-width: 768px) {
    .s-xxxxxx {
      width: 100%;
    }
    .s-xxxxxx:hover {
      background-color: #00F;
    }
  }
</style>
<div class="s-xxxxxx">Hello, world!</div>

Details

Basic inline styling

This plugin determines extraction targets only when all keys and values are literals.

// ✅ Extractable
function Component () {
  return (
    <div style={{
      width: 200,
    }} />
  )
}

// ⛔️ Not extractable (dynamic property)
function Component (props: { size: number }) {
  return (
    <div style={{
      width: props.size,
    }} />
  )
}

But literals that can be evaluated at build time, arithmetic operations on literals, and the spread of literal constant objects are included in the extraction target.

When using object spreads, it does not verify whether the object is immutable. If you are using TypeScript, it is highly recommended to set the object as const.

// ✅ Extractable
function Component () {
  return (
    <div style={{
      width: 200 + 100,
    }} />
  )
}

// ✅ Extractable
const style = {
  height: 200,
} as const
function Component () {
  return (
    <div style={{
      width: 200,
      ...style,
    }} />
  )
}

// ✅ Extractable
export const colors = {
  red: '#F00',
} as const
import { colors } from './colors'
function Component () {
  return (
    <div style={{
      backgroundColor: colors.red,
    }} />
  )
}

// 👀 Extractable, but cautious (no `as const`)
const invalidStyle = {
  height: 200,
}
function Component () {
  return (
    <div style={{
      ...invalidStyle,
    }} />
  )
}

Numeric units

When extracting numeric units, the plugin will convert some of them to px. Here are the properties that are converted:

export const pixelUnits = [
  'top', 'right', 'bottom', 'left',
  'gap', 'columnGap', 'rowGap',
  'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight',
  'margin', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom',
  'padding', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom',
  'borderWidth', 'borderTopWidth', 'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth',
  'borderRadius',
  'borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius',
  'fontSize', 'lineHeight', 'letterSpacing', 'wordSpacing', 'textIndent',
  'textShadowOffsetX', 'textShadowOffsetY', 'textShadowBlur',
  'boxShadowOffsetX', 'boxShadowOffsetY', 'boxShadowBlur',
]

// Example
function Component () {
  return (
    <div style={{
      borderTopLeftRadius: 10, // Converted (border-top-left-radius: 10px)
      flex: 1, // Not converted (flex: 1)
    }} />
  )
}

Pseudo-classes and media queries

In modern web apps, you'll often use pseudo-classes and media queries. This plugin enables the use of pseudo-classes and media queries in inline styles.

function Component () {
  return (
    <div style={{
      backgroundColor: '#F00',
      ':hover': {
        backgroundColor: '#0F0',
      },
    }} />
  )
}

function Component () {
  return (
    <div style={{
      width: 640,
      '@media (min-width: 768px)': {
        width: '100%',
      },
    }} />
  )
}

Media queries cannot be declared inside pseudo-classes or nested declarations.

// ✅ Allowed
function Component () {
  return (
    <div style={{
      '@media (min-width: 768px)': {
        ':hover': {
          backgroundColor: '#00F',
        },
      },
    }} />
  )
}

// ⛔️ Not allowed (nested media queries)
function Component () {
  return (
    <div style={{
      '@media (min-width: 768px)': {
        '@media (min-width: 1024px)': {
          backgroundColor: '#00F',
        },
      },
    }}/>
  )
}

// ⛔️ Not allowed (media query inside pseudo-class)
function Component () {
  return (
    <div style={{
      ':hover': {
        '@media (min-width: 1024px)': {
          backgroundColor: '#00F',
        },
      },
    }}/>
  )
}

Keyframes

Keyframes have a different nature from media queries. They must be declared in the global scope and applied to specific elements, but their names must be set as CSS property values. Therefore, this plugin allows objects to be used for the animation properties.

The fields of the animation object correspond to each animation-* property by default. Among these, the name field is assigned an automatically generated name based on the keyframe object passed as its value.

Additionally, we added a field named keyframes as an alias for the name field to provide a more intuitive representation. Either can be used, but if both are provided, the plugin prioritizes keyframes.

function Component () {
  return (
    <div style={{
      animation: {
        duration: '1s',
        keyframes: {
          from: {
            opacity: 0,
          },
          to: {
            opacity: 1,
          },
        },
      }
    }} />
  )
}

// Same as above, but with all animation properties set explicitly
function Component () {
  return (
    <div style={{
      animation: {
        duration: '1s',
        keyframes: {
          from: {
            opacity: 0,
          },
          to: {
            opacity: 1,
          },
        },
        // Default values for other properties
        composition: 'replace',
        delay: '0s',
        direction: 'normal',
        fillMode: 'none',
        iterationCount: 1,
        playState: 'running',
        range: 'normal',
        rangeStart: 'normal',
        rangeEnd: 'normal',
        timeline: 'none',
        timingFunction: 'ease',
      },
    }} />
  )
}

When both the animation property and animation-* properties are declared simultaneously, animation-* takes precedence, as is the case with standard CSS. Since the animation object supports everything that animation-* can set, it is recommended to use animation.

function Component () {
  return (
    <div style={{
      animation: {
        duration: '1s',
        keyframes: {
          from: {
            opacity: 0,
          },
          to: {
            opacity: 1,
          },
        }
      },
      animationDuration: '2s', // Not recommended, overrides the duration set in the animation object
    }} />
  )
}

Unsupported things

Other features like :root and @font-face, it is recommended to keep these in separate CSS files that are imported globally. There is no reason to handle them as inline styles.

License

MIT