Skip to content

webeach/react-x

Repository files navigation

react-x

npm package build npm downloads

🇺🇸 English version | 🇷🇺 Русская версия

A React extension library that adds convenient syntax for working with CSS classes, styles, and CSS variables directly in JSX.


💎 Features

  • 🎨 Conditional CSS classes — add classes via class:className with CSS Modules support
  • 💅 Inline styles as props — set styles via style:property
  • 🔧 CSS variables — pass CSS custom properties via var:variableName
  • 📋 classList — array of classes instead of string concatenation
  • 🏷️ All HTML/SVG tags — ready-to-use components x.div, x.span, x.svg, etc.
  • 🔄 HOC for components — wrap your components with x(Component)
  • 📝 Full TypeScript support — out of the box

📦 Installation

npm install @webeach/react-x

or

pnpm install @webeach/react-x

or

yarn add @webeach/react-x

🚀 Quick Start

import { x } from '@webeach/react-x';

function App() {
  const isActive = true;
  const isDisabled = false;

  return (
    <x.div
      class:container={true}
      class:active={isActive}
      class:disabled={isDisabled}
      style:padding="20px"
      style:backgroundColor="#f0f0f0"
      var:primaryColor="#007bff"
    >
      Hello, World!
    </x.div>
  );
}

Result in DOM:

<div
  class="container active"
  style="padding: 20px; background-color: #f0f0f0; --primaryColor: #007bff;"
>
  Hello, World!
</div>

🛠 API

The x Object

The main export of the library. Works in two ways:

1. As a collection of extended HTML/SVG tags

import { x } from '@webeach/react-x';

// HTML tags
<x.div>...</x.div>
<x.span>...</x.span>
<x.button>...</x.button>
<x.input />
<x.form>...</x.form>

// SVG tags
<x.svg>...</x.svg>
<x.path />
<x.circle />
<x.rect />

All standard HTML and SVG tags are supported.

2. As a HOC for wrapping components

import { x } from '@webeach/react-x';

// Your component
const ButtonInternal = ({ className, style, children, ...props }) => (
  <button className={className} style={style} {...props}>
    {children}
  </button>
);

// Extended component
const Button = x(Button);

// Usage
<Button
  class:primary
  class:large={size === 'large'}
  style:borderRadius="8px"
  var:btnColor="blue"
>
  Click me
</Button>

Conditional CSS Classes: class:

Add CSS classes conditionally via props with the class: prefix.

<x.div
  class:visible={isVisible}      // added if isVisible === true
  class:hidden={!isVisible}      // added if isVisible === false
  class:active={isActive}
  class:error={hasError}
  class:my-custom-class          // kebab-case is supported
>
  Content
</x.div>

Supported value types:

  • booleantrue adds the class, false does not
  • string — used as an alias (class name from value, not from key)
  • null | undefined — class is not added

Usage with CSS Modules

Pass a string to use with CSS Modules — the value becomes the class name:

import styles from './Button.module.css';

<x.button
  class:base={styles.button}           // adds class from styles.button
  class:primary={styles.primary}       // adds class from styles.primary  
  class:disabled={isDisabled && styles.disabled}  // conditional
>
  Click me
</x.button>

If styles.button = "Button_button__x7f2s", the result:

<button class="Button_button__x7f2s Button_primary__a3bc1">
  Click me
</button>

Important: When passing a string, the value is used as the class name, not the key after class:.


Class Array: classList

An alternative way to pass multiple classes via an array:

const classes = ['card', 'card-primary', isLarge && 'card-large'];

<x.div classList={classes}>
  Content
</x.div>

Falsy array elements are automatically filtered out.

Can be combined with className and class::

<x.div
  className="base-class"
  classList={['additional', 'classes']}
  class:conditional
>
  All three methods work together
</x.div>
// Result: class="base-class additional classes conditional"

Inline Styles: style:

Set CSS properties directly via props with the style: prefix.

<x.div
  style:display="flex"
  style:justifyContent="center"
  style:alignItems="center"
  style:gap="10px"
  style:padding="20px"
  style:backgroundColor="#f5f5f5"
  style:borderRadius="8px"
>
  Flex container
</x.div>

Advantages:

  • IDE autocompletion for CSS property names
  • Typed values
  • Clean syntax without nested objects

Can be combined with regular style:

<x.div
  style={{ margin: '10px' }}
  style:padding="20px"
  style:color="red"
>
  Styles are merged
</x.div>

CSS Variables: var:

Pass CSS custom properties (variables) via props with the var: prefix.

<x.div
  var:primaryColor="#007bff"
  var:secondaryColor="#6c757d"
  var:spacing="16px"
  var:columns={3}
>
  <x.span style:color="var(--primaryColor)">
    Text in primary color
  </x.span>
</x.div>

Result in DOM:

<div style="--primaryColor: #007bff; --secondaryColor: #6c757d; --spacing: 16px; --columns: 3;">
  <span style="color: var(--primaryColor);">
    Text in primary color
  </span>
</div>

Supported value types:

  • stringvar:color="red"
  • numbervar:columns={3}
  • null — to reset a variable

CSS Variables Typing

You can type available CSS variables for a component:

import { ReactNode } from 'react';
import { x } from '@webeach/react-x';

// Define variable types
type ThemeVars = {
  primaryColor: string;
  secondaryColor: string;
  spacing: number;
};

// Typed component
const ThemedBox = x<{ children: ReactNode }, ThemeVars>(
  ({ children, className, style }) => (
    <div className={className} style={style}>
      {children}
    </div>
  )
);

// Now IDE suggests available variables
<ThemedBox
  var:primaryColor="blue"    // ✓ autocompletion works
  var:secondaryColor="gray"  // ✓
  var:spacing={16}           // ✓
  var:unknownVar="value"     // ✗ TypeScript error
>
  Content
</ThemedBox>

📥 Usage Examples

Button with Variants

import { ReactNode } from 'react';
import { x } from '@webeach/react-x';

type ButtonProps = {
  variant?: 'primary' | 'secondary' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
  children: ReactNode;
};

function Button({ variant = 'primary', size = 'medium', disabled, children }: ButtonProps) {
  return (
    <x.button
      class:btn
      class:btn-primary={variant === 'primary'}
      class:btn-secondary={variant === 'secondary'}
      class:btn-danger={variant === 'danger'}
      class:btn-sm={size === 'small'}
      class:btn-lg={size === 'large'}
      class:disabled={disabled}
      disabled={disabled}
    >
      {children}
    </x.button>
  );
}

Button with CSS Modules

import { ReactNode } from 'react';
import { x } from '@webeach/react-x';

import styles from './Button.module.css';

type ButtonProps = {
  variant?: 'primary' | 'secondary';
  disabled?: boolean;
  children: ReactNode;
};

function Button({ variant = 'primary', disabled, children }: ButtonProps) {
  return (
    <x.button
      class:base={styles.button}
      class:variant={variant === 'primary' ? styles.primary : styles.secondary}
      class:disabled={disabled && styles.disabled}
      disabled={disabled}
    >
      {children}
    </x.button>
  );
}

Themed Card

import { x } from '@webeach/react-x';

function Card({ title, children, accentColor = '#007bff' }) {
  return (
    <x.article
      class:card
      var:accentColor={accentColor}
      style:borderLeft="4px solid var(--accentColor)"
      style:padding="20px"
      style:borderRadius="8px"
      style:boxShadow="0 2px 8px rgb(0 0 0 / 0.1)"
    >
      <x.h2 style:color="var(--accentColor)" style:marginTop="0">
        {title}
      </x.h2>
      {children}
    </x.article>
  );
}

Responsive Grid

import { x } from '@webeach/react-x';

function Grid({ columns = 3, gap = '16px', children }) {
  return (
    <x.div
      style:display="grid"
      style:gridTemplateColumns={`repeat(${columns}, 1fr)`}
      style:gap={gap}
    >
      {children}
    </x.div>
  );
}

SVG Icon

import { x } from '@webeach/react-x';

function Icon({ size = 24, color = 'currentColor' }) {
  return (
    <x.svg
      var:iconSize={`${size}px`}
      var:iconColor={color}
      style:width="var(--iconSize)"
      style:height="var(--iconSize)"
      viewBox="0 0 24 24"
      fill="none"
      stroke="var(--iconColor)"
      strokeWidth="2"
    >
      <x.path d="M12 2L2 7l10 5 10-5-10-5z" />
      <x.path d="M2 17l10 5 10-5" />
      <x.path d="M2 12l10 5 10-5" />
    </x.svg>
  );
}

📐 Exported Types

import type { XClassProps, XStyleProps, XVarProps } from '@webeach/react-x';
  • XClassProps — types for class:* props
  • XStyleProps — types for style:* props (based on CSSProperties)
  • XVarProps<T> — types for var:* props with optional variable typing

⚖️ Comparison with Alternatives

Feature react-x clsx/classnames styled-components
Conditional classes class:active={bool} clsx({ active: bool }) ${bool && 'active'}
CSS Modules class:x={styles.class} clsx(styles.class)
Inline styles style:padding="10px" Built-in
CSS variables var:color="red" ${props => props.color}
TypeScript ✅ Full
Runtime overhead Minimal Minimal CSS-in-JS
Bundle size ~2KB ~1KB ~15KB

🧩 Dependencies

This library has no external dependencies (Zero dependencies).


🔖 Releasing a New Version

Releases are handled automatically via semantic-release.

Before publishing a new version, ensure that:

  1. All changes are committed and pushed to the main branch.
  2. Commit messages follow the Conventional Commits format:
    • feat: ... — for new features
    • fix: ... — for bug fixes
    • chore: ..., refactor: ... and other types — as needed
  3. Versioning is determined automatically based on commit types (patch, minor, major).

👨‍💻 Author

Development and maintenance: Ruslan Martynov

If you have suggestions or found a bug, open an issue or submit a pull request.


📄 License

This package is distributed under the MIT License.

About

Extended React JSX syntax for conditional classes, inline styles, and CSS variables

Topics

Resources

License

Stars

Watchers

Forks

Contributors 2

  •  
  •