Skip to content

chbybnwr/vicinage

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

98 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Vicinage · npm version build GitHub license

Type-safe and zero-runtime UI styling, right in the markup.

Vicinage lets you write strongly-typed CSS objects directly on your markup. At build time, it preprocesses them into StyleX API calls, which StyleX then extracts into zero-runtime atomic CSS, with no style block naming required.

Table of Contents

Quick Start

Setup

Install the packages:

npm install vicinage @stylexjs/stylex
npm install --save-dev @vicinage/unplugin @stylexjs/unplugin

Add the plugin to your bundler configuration right before the StyleX plugin.

import { defineConfig } from 'vite'
import vicinage from '@vicinage/unplugin'
import stylex from '@stylexjs/unplugin'

export default defineConfig({
  plugins: [
    vicinage.vite(),
    stylex.vite(),
    // ...other plugins
  ],
})

Ecosystem

  • Universal bundler plugin

  • ESLint plugin

  • Design tokens from SolarWind CSS

  • Chrome Extension StyleX DevTools

  • VS Code extension Explicit Folding

    Recommended settings
    {
      "[javascriptreact][typescriptreact]": {
        "editor.defaultFoldingRangeProvider": "zokugun.explicit-folding",
        "explicitFolding.rules": [
          {
            "beginRegex": "^\\s*<[a-zA-Z][a-zA-Z0-9-]*",
            "endRegex": "(?<!=)>$",
            "autoFold": true,
            "foldLastLine": true
          }
        ]
      }
    }

Usage

Element styling

Apply styles directly to HTML elements.

import { apply } from 'vicinage'

function App() {
  return (
    <div
      {...apply({
        color: 'green',
        backgroundColor: 'black',
      })}
    >
      hello, world
    </div>
  )
}

Component styling

Pass styles with the sheet() function to components that accept StyleXStyles.

import { apply, sheet } from 'vicinage'
import type { StyleXStyles } from '@stylexjs/stylex'

function Feed() {
  return (
    <Post
      style={sheet({
        color: 'blue',
      })}
    />
  )
}

function Post({ style }: { style?: StyleXStyles }) {
  return (
    <div
      {...apply(
        {
          color: 'black',
        },
        style,
      )}
    >
      Lorem ipsum
    </div>
  )
}

Responsive styling

import { apply } from 'vicinage'

function Hero() {
  return (
    <h1
      {...apply({
        fontSize: {
          default: '1.5rem',
          '@media (min-width: 768px)': '2.25rem',
        },
      })}
    >
      Welcome back
    </h1>
  )
}

Pseudo-classes

import { apply } from 'vicinage'

function BillingLink() {
  return (
    <a
      {...apply({
        color: {
          default: 'blue',
          ':visited': 'purple',
        },
      })}
      href="/billing/"
    >
      Open billing
    </a>
  )
}

Pseudo-elements

import { apply } from 'vicinage'

function SearchInput() {
  return (
    <input
      placeholder="Search"
      {...apply({
        color: 'black',
        '::placeholder': {
          color: 'gray',
        },
      })}
    />
  )
}

Variables

Custom properties for app-level theming and inline overrides:

/* main.css */
:root {
  --sidebar-width: 240px;
  --color-surface: lightblue;
}
import { apply } from 'vicinage'

function Sidebar() {
  return (
    <nav
      {...apply({
        '--sidebar-width': '320px',
        width: 'var(--sidebar-width)',
        backgroundColor: 'var(--color-surface, blue)',
      })}
    >
      Navigation
    </nav>
  )
}

StyleX variables for shared design tokens:

// tokens.stylex.ts
import * as stylex from '@stylexjs/stylex'

export const color = stylex.defineVars({
  primary: 'blue',
})
import { apply } from 'vicinage'
import { color } from './tokens.stylex'

function PrimaryButton({ label }: { label: string }) {
  return (
    <button
      {...apply({
        backgroundColor: color.primary,
      })}
    >
      {label}
    </button>
  )
}

Cascade with StyleX styles

import { apply } from 'vicinage'
import * as stylex from '@stylexjs/stylex'

const typography = stylex.create({
  caption: {
    fontSize: '0.75rem',
    lineHeight: '1rem',
    fontStyle: 'italic',
  },
})

function Timestamp() {
  return (
    <time
      {...apply(
        {
          color: 'black',
        },
        typography.caption,
      )}
    >
      2 minutes ago
    </time>
  )
}

Conditional styling

import { apply } from 'vicinage'

function SaveButton({ isEnabled }: { isEnabled: boolean }) {
  return (
    <button
      {...apply({
        fontWeight: isEnabled && 'bold',
        backgroundColor: isEnabled ? 'blue' : 'gray',
      })}
    >
      Save changes
    </button>
  )
}

Dynamic styling

Use function to define runtime dynamic values.

import { apply } from 'vicinage'

function ProgressBar({ percentage }: { percentage: number }) {
  return (
    <div
      {...apply({
        width: () => `${percentage}%`,
        height: '16px',
        backgroundColor: 'blue',
      })}
    />
  )
}

Dynamic values can also be deeply nested.

import { apply } from 'vicinage'

function Swatch({ hue }: { hue: number }) {
  return (
    <div
      {...apply({
        backgroundColor: {
          default: () => `hsl(${hue}, 60%, 50%)`,
          ':hover': () => `hsl(${hue}, 80%, 40%)`,
        },
      })}
    />
  )
}

NOTE: The function body must be an expression statement. You cannot use a function body with block statement.

About

Type-safe and zero-runtime UI styling, right in the markup.

Topics

Resources

License

Stars

Watchers

Forks

Sponsor this project

 

Contributors