DEV Community

Cover image for Introducing Varkit: Dynamic CSS Variables for React with TypeScript
Kunal Tanwar
Kunal Tanwar

Posted on

Introducing Varkit: Dynamic CSS Variables for React with TypeScript

Introducing Varkit 🎨

I'm excited to share my first open-source library: Varkit - a lightweight solution for managing CSS variables in React with full TypeScript support!

The Problem πŸ€”

While building React applications, I kept running into the same frustrations with styling:

  1. Prop drilling for theme values - Passing colors and sizes through multiple components
  2. Separate CSS files for pseudo-classes - :hover, :focus states living far from component logic
  3. No type safety - Typos in CSS variable names only caught at runtime
  4. Heavy CSS-in-JS libraries - 15KB+ just for styling

I wanted something lightweight, type-safe, and intuitive. That's why I built Varkit.


What is Varkit? ✨

Varkit is a 6.4KB library that lets you define and manipulate CSS variables directly in React components with full TypeScript support.

Key Features:

  • 🎯 CSS variables with __ prefix - Clean, intuitive syntax
  • 🎨 Pseudo-class support with _ prefix - :hover, :focus, :active, and 50+ more
  • πŸ”„ Function-based dynamic values - Compute values on the fly
  • πŸ“¦ Zero dependencies - Minimal bundle impact
  • πŸ’ͺ Full TypeScript support - Catch errors before runtime
  • 🧩 Works with all HTML elements - Via Proxy magic

Installation πŸ“¦

npm install @kunaltanwar/varkit
# or
pnpm add @kunaltanwar/varkit
# or
yarn add @kunaltanwar/varkit
Enter fullscreen mode Exit fullscreen mode

Quick Start πŸš€

Here's a simple button with hover effects:

import { varkit } from '@kunaltanwar/varkit'

function App() {
  return (
    <varkit.button
      style={{
        // Define CSS variables
        __bg: 'blue',
        __color: 'white',
        __padding: '12px 24px',

        // Use them in regular CSS
        backgroundColor: 'var(--bg)',
        color: 'var(--color)',
        padding: 'var(--padding)',
        border: 'none',
        borderRadius: '8px',
        cursor: 'pointer',
        fontSize: '16px',

        // Pseudo-classes
        _hover: {
          __bg: 'darkblue',
          transform: 'scale(1.05)',
        },
        _active: {
          __bg: 'navy',
        },
      }}
    >
      Click Me!
    </varkit.button>
  )
}
Enter fullscreen mode Exit fullscreen mode

That's it! No separate CSS files, no complex setup.


Real-World Examples πŸ’‘

1. Theme Switcher

function Card({ theme = 'light' }) {
  const themes = {
    light: { bg: '#fff', text: '#000', border: '#ddd' },
    dark: { bg: '#222', text: '#fff', border: '#555' },
  }

  const { bg, text, border } = themes[theme]

  return (
    <varkit.div
      style={{
        __bg: bg,
        __text: text,
        __border: border,

        backgroundColor: 'var(--bg)',
        color: 'var(--text)',
        border: '1px solid var(--border)',
        padding: '20px',
        borderRadius: '8px',
        transition: 'all 0.3s',
      }}
    >
      <h2>Themed Card</h2>
      <p>Switch between light and dark mode!</p>
    </varkit.div>
  )
}
Enter fullscreen mode Exit fullscreen mode

2. Interactive Form Input

function Input({ error }) {
  return (
    <varkit.input
      style={{
        __borderColor: error ? 'red' : '#ccc',
        __focusColor: error ? 'darkred' : 'blue',

        border: '2px solid var(--border-color)',
        padding: '10px',
        borderRadius: '4px',
        outline: 'none',
        transition: 'all 0.2s',

        _focus: {
          __borderColor: 'var(--focus-color)',
          boxShadow: '0 0 0 3px rgba(0,0,255,0.1)',
        },
      }}
    />
  )
}
Enter fullscreen mode Exit fullscreen mode

3. Animated Card (with Function-Based Values)

function AnimatedCard() {
  return (
    <varkit.div
      style={{
        __scale: 1,
        __rotate: '0deg',

        width: '200px',
        height: '200px',
        backgroundColor: '#4CAF50',
        transform: 'scale(var(--scale)) rotate(var(--rotate))',
        transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',

        _hover: {
          __scale: 1.1,
          __rotate: '5deg',
          boxShadow: '0 10px 40px rgba(0,0,0,0.2)',
        },
      }}
    >
      <p>Hover me!</p>
    </varkit.div>
  )
}
Enter fullscreen mode Exit fullscreen mode

How It Works πŸ”§

Varkit uses a clever approach:

  1. Parses your style object - Identifies CSS variables (__prefix) and pseudo-classes (_prefix)
  2. Generates CSS rules - Converts to standard CSS with proper selectors
  3. Injects styles - Adds a <style id="varkit"> tag to the document
  4. Deduplicates - Uses hash-based caching to avoid redundant CSS

Example transformation:

<varkit.div style={{ __bg: 'red' }}>Content</varkit.div>
Enter fullscreen mode Exit fullscreen mode

Becomes:

<div data-vk="vk-abc123">Content</div>

<style id="varkit">
  [data-vk="vk-abc123"] {
    --bg: red;
  }
</style>
Enter fullscreen mode Exit fullscreen mode

Why Choose Varkit? 🎯

vs Styled-Components

Feature Varkit Styled-Components
Size 6.4 KB 15+ KB
Runtime Minimal Heavy
CSS Variables βœ… Native ❌ Manual
Learning Curve Easy Moderate
TypeScript βœ… Built-in ⚠️ Requires setup

vs Emotion

Feature Varkit Emotion
Size 6.4 KB 10+ KB
Syntax Inline styles CSS strings
CSS Variables βœ… Native ⚠️ Limited
Setup None Config needed

vs Vanilla CSS

Feature Varkit Vanilla CSS
Colocation βœ… Component-level ❌ Separate files
Dynamic βœ… JavaScript ❌ Static
Type-safe βœ… TypeScript ❌ No types
Maintenance βœ… Easy ⚠️ Can drift

Advanced Features πŸš€

Supported Pseudo-Classes

Varkit supports 50+ pseudo-classes:

  • Interactive: :hover, :focus, :active, :disabled
  • Form states: :checked, :invalid, :valid, :required
  • Structural: :first-child, :last-child, :only-child
  • Text: :empty, :read-only, :read-write
  • And many more!

TypeScript Support

Full type safety out of the box:

import { varkit, VarkitProps, VarkitStyles } from '@kunaltanwar/varkit'

// Type-safe styles
const styles: VarkitStyles = {
  __bg: 'red',
  _hover: {
    __bg: 'blue',  // βœ… Type-checked
  },
}

// Type-safe component props
type ButtonProps = VarkitProps<'button'> & {
  variant: 'primary' | 'secondary'
}
Enter fullscreen mode Exit fullscreen mode

Performance πŸ“Š

Varkit is designed for performance:

  • βœ… Automatic deduplication - Same styles = one CSS rule
  • βœ… Hash-based caching - Rules injected only once
  • βœ… Minimal overhead - Just string manipulation
  • βœ… No runtime CSS parsing - Pre-computed at render

Bundle impact: Only 6.4 KB gzipped!


Roadmap πŸ—ΊοΈ

I'm actively working on:

v1.1

  • Functional pseudo-classes (:nth-child, :has, :not)
  • Animation keyframes support
  • Media query support
  • Container queries

v1.2

  • Server-side rendering (SSR)
  • Next.js App Router compatibility
  • Theme provider component
  • CSS extraction for production

v2.0 (Maybe)

  • Babel plugin for optimizations
  • VS Code extension
  • Migration tools from styled-components/Emotion

See full roadmap


Get Started Today! πŸŽ‰

npm install @kunaltanwar/varkit
Enter fullscreen mode Exit fullscreen mode

Links:


Contributing 🀝

Varkit is open-source and contributions are welcome!

  • ⭐ Star the repo if you find it useful
  • πŸ› Report bugs and suggest features
  • πŸ”§ Submit pull requests
  • πŸ“ Improve documentation
  • πŸ’¬ Join discussions

Final Thoughts πŸ’­

Building Varkit taught me a lot about:

  • React internals and Proxies
  • TypeScript library design
  • CSS variable manipulation
  • npm package publishing
  • Open-source best practices

I hope it solves styling challenges for you too!

Try it out and let me know what you think! Feedback is always welcome. πŸ™Œ


If you found this helpful, please give it a ❀️ and share with your React developer friends!

Top comments (0)