Giter VIP home page Giter VIP logo

css-vars-hook's Introduction

Node.js CI yarn version npm types included npm bundle size

CSS Variables React hook

css-vars-hook contains React hooks to set and manipulate CSS custom properties from React component.

Demo

dev.to article

Features

  • Set, modify and delete CSS Custom Properties inside React components.
  • Set up and manage CSS themes for the whole application.
  • Apply CSS themes (multiple variables) to any HTMLElement.
  • Written in Typescript.
  • Zero dependencies.
  • Small and fast.

Install

npm install css-vars-hook

Usage

css-vars-hook exposes two hooks: useRootTheme, useLocalTheme.

useRootTheme

useRootTheme applies application level themes. API consists of two elements: the hook itself and RootThemeProvider component which acts as :root selector. Directly applying theme to the :root is not compatible with Server side rendering (SSR). See API docs.

Manipulate theme

Set up

In order to set global theming you need to wrap your application with RootThemeProvider on highest possible level.

// App.js
import React from 'react';
import {RootThemeProvider} from 'css-vars-hook';

// Theme object contains dictionary of CSS variables you will use later in your application
const theme = {
    boxColor: 'purple',
    borderColor: 'violet',
}

export const App = () => (
    <RootThemeProvider
        theme={theme}>
        {/*...*/}
    </RootThemeProvider>
);

Memoize theme

To avoid unnecessary reconciliations and re-renders theme object has to preserve referential equality during component lifecycle.

// Wrong!!! Component will rerender every time

const ComponentA: FC = () => {
    const theme = {
        foo: 'bar'
    }

    return <RootThemeProvider theme={theme}>{/*...*/}</RootThemeProvider> 
}

// Wrong!!! Component will rerender every time

const ComponentB: FC = () => {
    return <RootThemeProvider theme={{ foo: 'bar' }}>{/*...*/}</RootThemeProvider>
}

// Correct!!! Theme will preserve untill one of its' properties change

const ComponentC: FC<{foo: string}> = ({foo}) => {
    
    const theme = useMemo(() => ({foo}), [foo])
    
    return <RootThemeProvider theme={theme}>{/*...*/}</RootThemeProvider>
}

// Correct!!! Theme is external and static in relation to component

const themeD = {
    foo: 'bar'
}

const ComponentD: FC = () => {
    return <RootThemeProvider theme={themeD}>{/*...*/}</RootThemeProvider>
}

Change theme

Theme changing methods (setTheme, setVariable, removeVariable) are implemented as effects. They will apply after component re-render. You'll have to wrap the side effect with useEffect or put in inside callback to move it out of the rendering calculation.

// Component.jsx
import React, { useEffect, useCallback } from "react";
import { useRootTheme } from 'css-vars-hook';

const theme = {
  boxColor: 'red',
  borderColor: 'green',
}

const Component = () => {
  const { setTheme, setVariable, removeVariable } = useRootTheme();

  // Set theme value inside useEffect hook
  useEffect(() => {
    // Theme changing effects can be applied like this. The change will happen after render.
    setTheme(theme);
  }, [theme, setTheme])

  // Set theme value inside callback
  const handleVariable = useCallback(() => {
    setVariable('boxColor', 'pink');
  }, [])

  return <button onClick={handleVariable}>Change variable</button>;
}

Caveats

//...
const Component = () => {
  const { setTheme } = useRootTheme();

  // This will not work!
  setTheme(theme)

  //...
}

The reason this code isn’t correct is that it tries to do something with the DOM node during rendering. In React, rendering should be a pure calculation of JSX and should not contain side effects like modifying the DOM. Moreover, when Component is called for the first time, its DOM does not exist yet, so there is no theme container to operate with.

Type safety

Global theme type should be defined on a project level. You'll have to redeclare ThemeType export from css-vars-hook

// types.d.ts
import theme from '@/theme';

declare module 'css-vars-hook' {
    // Provide your global theme type here
    export type ThemeType = typeof theme;
}

Consume the theme data

CSS variables set by RootThemeProvider are available globally across all application.

In CSS

// Component.css

.box {
    background: var(--boxColor);
    border: 1px solid var(--borderColor)
}

In JS

import {useRootTheme} from 'css-vars-hook';

const {
    /** Get current theme */
    getTheme,
    /** Get variable value within active theme */
    getVariable,
} = useRootTheme();

console.log(getVariable('boxColor')) // => 'purple'
console.log(getTheme()) // => theme object

useLocalTheme

useLocalTheme applies theme locally to the wrapped React components.

Set up a local theme

In order to set local theme you need to wrap your component with LocalRoot component which is returned by useLocalTheme hook.

import { useLocalTheme } from 'css-vars-hook';
import { useCallback } from "react";

const theme = { boxColor: 'yellow' };

const Component = () => {
  const { LocalRoot, setTheme } = useLocalTheme();
  const setDarkMode = useCallback(() => {
    setTheme({boxColor: 'darkYellow'})
  }, []);
  return <LocalRoot theme={theme}>{/*...*/}</LocalRoot>
}

Outside different wrapping strategies this hook is similar to useRootTheme.

Customize LocalRoot element

By default LocalRoot is rendered as a div HTMLElement. You can provide custom element type (button, span, e. t. c.) by changing as prop of LocalRoot.

import {useLocalTheme} from 'css-vars-hook';

const theme = {boxColor: 'yellow'};

const Component = () => {
    const {LocalRoot: Button, setTheme} = useLocalTheme();
    const setDarkMode = useCallback(() => {
      setTheme({boxColor: 'darkYellow'})
    }, [])
    return (
      <Button 
        theme={theme} 
        as="button" 
        onClick={setDarkMode}>
        Set dark mode
      </Button>
    )
}

Type safety

Local theme type is inferred from corresponding LocalRoot prop.

css-vars-hook's People

Contributors

dependabot[bot] avatar kurtgokhan avatar morewings avatar renovate[bot] avatar semantic-release-bot avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

css-vars-hook's Issues

Refactor Demo page

At the moment, demo page uses logic which requires React to rerender components. Needs to be changed to non-rendering techniques.

Refactor useTheme

  • Make consistent with useRootTheme
  • Add docs and examples for prop bindings to achieve similar functionality as CSS in js.

getVariable gets token value from local or global scope. Maybe needs to be wrapped in useEffect

Improve docs add title to local and global examples

  • add title to local and global examples
  • add note about useEffect to setVariable and setTheme

The reason this code isn’t correct is that it tries to do something with the DOM node during rendering. In React, rendering should be a pure calculation of JSX and should not contain side effects like modifying the DOM.
Moreover, when VideoPlayer is called for the first time, its DOM does not exist yet! There isn’t a DOM node yet to call play() or pause() on, because React doesn’t know what DOM to create until you return the JSX.

The solution here is to wrap the side effect with useEffect to move it out of the rendering calculation:
By wrapping the DOM update in an Effect, you let React update the screen first. Then your Effect runs.

When your VideoPlayer component renders (either the first time or if it re-renders), a few things will happen. First, React will update the screen, ensuring the

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Awaiting Schedule

These updates are awaiting their schedule. Click on a checkbox to get an update now.

  • chore(deps): update dependency @semantic-release/npm to v12.0.1
  • chore(deps): update dependency @testing-library/jest-dom to v6.4.5
  • chore(deps): update dependency @types/lodash to v4.17.1
  • chore(deps): update dependency eslint-plugin-react-refresh to v0.4.7
  • chore(deps): update dependency postcss-preset-env to v9.5.11
  • chore(deps): update dependency vite to v5.2.11
  • chore(deps): update dependency vite-plugin-dts to v3.9.1
  • chore(deps): update dependency stylelint to v16.5.0
  • chore(deps): update dependency @testing-library/react to v15
  • chore(deps): update dependency eslint to v9

Detected dependencies

github-actions
.github/workflows/merge-jobs.yml
  • actions/checkout v4
  • styfle/cancel-workflow-action 0.12.1
  • actions/setup-node v4
  • pnpm/action-setup v2
  • actions/cache v4
.github/workflows/pages.yml
  • actions/checkout v4
  • styfle/cancel-workflow-action 0.12.1
  • actions/setup-node v4
  • pnpm/action-setup v3
  • actions/cache v4
  • actions/configure-pages v5
  • actions/upload-pages-artifact v3
  • actions/deploy-pages v4
.github/workflows/pull-request-jobs.yml
  • actions/checkout v4
  • styfle/cancel-workflow-action 0.12.1
  • actions/setup-node v4
  • pnpm/action-setup v3
  • actions/cache v4
npm
package.json
  • @commitlint/cli 19.3.0
  • @commitlint/config-conventional 19.2.2
  • @semantic-release/commit-analyzer 12.0.0
  • @semantic-release/git 10.0.1
  • @semantic-release/github 10.0.3
  • @semantic-release/npm 12.0.0
  • @semantic-release/release-notes-generator 13.0.0
  • @testing-library/jest-dom 6.4.2
  • @testing-library/react 14.3.1
  • @types/jest 29.5.12
  • @types/lodash 4.17.0
  • @types/react 18.3.1
  • @types/react-dom 18.3.0
  • @typescript-eslint/eslint-plugin 7.8.0
  • @typescript-eslint/parser 7.8.0
  • @vitejs/plugin-react 4.2.1
  • @yelo/rollup-node-external 1.0.1
  • alias-hq 6.2.3
  • commitizen 4.3.0
  • commitlint 19.3.0
  • cz-conventional-changelog 3.3.0
  • classnames 2.5.1
  • eslint 8.57.0
  • eslint-config-prettier 9.1.0
  • eslint-plugin-import 2.29.1
  • eslint-plugin-prettier 5.1.3
  • eslint-plugin-react 7.34.1
  • eslint-plugin-react-hooks 4.6.2
  • eslint-plugin-react-refresh 0.4.6
  • eslint-plugin-ssr-friendly 1.3.0
  • eslint-plugin-storybook 0.8.0
  • husky 9.0.11
  • identity-obj-proxy 3.0.0
  • is-ci 3.0.1
  • jest 29.7.0
  • jest-environment-jsdom 29.7.0
  • lint-staged 15.2.2
  • npm-run-all2 6.1.2
  • postcss 8.4.38
  • postcss-preset-env 9.5.9
  • prettier 3.2.5
  • react 18.3.1
  • react-dom 18.3.1
  • semantic-release 23.0.8
  • stylelint 16.4.0
  • stylelint-config-standard 36.0.0
  • stylelint-order 6.0.4
  • stylelint-prettier 5.0.0
  • ts-jest 29.1.2
  • ts-node 10.9.2
  • typescript 5.4.5
  • vite 5.2.10
  • vite-plugin-dts 3.9.0
  • react >=18.2.0
  • react-dom >=18.2.0
  • node >=16
nvm
.nvmrc
  • node v20

  • Check this box to trigger a request for Renovate to run again on this repository

Add options support

useLocalTheme({case, ref})

<Provider case=“camel|kebab” ref={ref}/>

  1. External ref
  2. Different variable naming

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.