Skip to content

Instantly share code, notes, and snippets.

@itsjavi
Last active January 26, 2023 06:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itsjavi/aa636c02737509a09be117204b07ddcc to your computer and use it in GitHub Desktop.
Save itsjavi/aa636c02737509a09be117204b07ddcc to your computer and use it in GitHub Desktop.
Simple CSS-in-JS implementation for React, using tagged templates, with scoped class name generation. Zero dependencies.
import React, { useEffect } from 'react'
type StyledFC = React.FC<{ children?: React.ReactNode }>
type StyledModule = <T extends string[]>(
...classNames: T
) => {
Styled: StyledFC
classMap: Record<T[number], string>
}
let cssModuleCounter = 0
function css(strings: TemplateStringsArray, ...values: any[]): StyledModule {
const cssStr = String.raw({ raw: strings }, ...values)
return (...classNames: string[]) => {
cssModuleCounter++
const moduleId =
Buffer.from(cssStr + classNames.join(','))
.toString('base64')
.replaceAll(/[^a-zA-Z0-9_-]/g, '')
.slice(1, 9) + `${cssModuleCounter}`
let cssStrModule = cssStr
let classMap: Record<string, string> = {}
classNames.forEach((className) => {
const newClassName = `${className}-${moduleId}`
classMap[className] = newClassName
cssStrModule = cssStrModule.replace(
new RegExp(`(\\.${className})([^a-zA-Z0-9_-])`, 'g'),
`.${newClassName}$2`
)
})
const Styled: StyledFC = (props) => {
useEffect(() => {
const styleId = 'supercss-' + moduleId
if (document.getElementById(styleId)) {
console.warn(`Stylesheet ${styleId} already exists. Skipping.`)
return
}
const style = document.createElement('style')
style.id = styleId
style.innerHTML = cssStrModule
document.head.appendChild(style)
return () => {
document.head.removeChild(style)
}
}, [])
if (!props || !props.children) {
return null
}
return <>{props.children}</>
}
return { Styled, classMap }
}
}
export default css
import Head from 'next/head'
import React from 'react'
type StyledFC = React.FC<{ children?: React.ReactNode }>
type StyledModule = <T extends string[]>(
...classNames: T
) => {
Styled: StyledFC
classMap: Record<T[number], string>
}
let cssModuleCounter = 0
function css(strings: TemplateStringsArray, ...values: any[]): StyledModule {
const cssStr = String.raw({ raw: strings }, ...values)
return (...classNames: string[]) => {
cssModuleCounter++
const moduleId =
Buffer.from(cssStr + classNames.join(','))
.toString('base64')
.replaceAll(/[^a-zA-Z0-9_-]/g, '')
.slice(1, 9) + `${cssModuleCounter}`
let cssStrModule = cssStr
let classMap: Record<string, string> = {}
classNames.forEach((className) => {
const newClassName = `${className}-${moduleId}`
classMap[className] = newClassName
cssStrModule = cssStrModule.replace(
new RegExp(`(\\.${className})([^a-zA-Z0-9_-])`, 'g'),
`.${newClassName}$2`
)
})
const Styled: StyledFC = (props) => {
const styleInjection = (
<Head>
<style
id={'supercss-' + moduleId}
dangerouslySetInnerHTML={{ __html: cssStrModule }}
/>
</Head>
)
if (!props || !props.children) {
return styleInjection
}
return (
<>
{styleInjection}
{props.children}
</>
)
}
return { Styled, classMap }
}
}
export default css
import Link from 'next/link'
import { useRouter } from 'next/router'
import css from './CSS-in-JS-withSSR'
import { cssVars } from './configs'
import { classNames } from './utils'
const { Styled, classMap } = css`
.root {
display: flex;
font-size: ${cssVars.fontSizes.xs};
background-color: ${cssVars.colors.dark1};
justify-content: space-between;
color: ${cssVars.colors.light2};
}
.root > div {
padding: ${cssVars.sizes.default};
}
.rightLinks a {
margin-left: ${cssVars.sizes.m};
}
.rightLinks a:hover,
.rightLinks a.active {
color: ${cssVars.colors.light1};
}
a.mainTitle {
text-decoration: none;
font-weight: bold;
font-size: 1.1rem;
background: #db5cfe;
background: linear-gradient(to right, #db5cfe 0%, #31fab9 100%);
background-clip: text;
-webkit-background-clip: text;
-moz-background-clip: text;
-ms-background-clip: text;
-webkit-text-fill-color: transparent;
-moz-text-fill-color: transparent;
-ms-text-fill-color: transparent;
line-height: 1;
}
a.mainTitle img {
vertical-align: middle;
display: inline-block;
aspect-ratio: 1;
width: 1.4rem;
height: 1.4rem;
line-height: 1;
margin-right: 0.8rem;
}
`('root', 'rightLinks', 'mainTitle')
export default function Example() {
const currentPath = useRouter().pathname
return (
<Styled>
<div className={classMap.root}>
<div>
<Link className={classMap.mainTitle} href="/">
<img src="/favicon.png" alt="logo" />
My Admin Tool
</Link>
</div>
<div className={classMap.rightLinks}>
<a
target="_blank"
rel="noreferrer"
href="https://github.com/itsjavi"
>
Github
</a>
<Link
href="/settings"
className={classNames([currentPath === '/settings', 'active'])}
>
Settings
</Link>
<Link
href="/about"
className={classNames([currentPath === '/about', 'active'])}
>
About
</Link>
</div>
</div>
</Styled>
)
}
@itsjavi
Copy link
Author

itsjavi commented Jan 14, 2023

  • Issues: each component will add an extra style element, duplicating them.
  • The CSS-in-JS-next.tsx version uses SSR and NextJS, to add the styles to the head in build time, not on component mount, so it's more convenient and faster.
  • You can also add <Styled /> anywhere in your component, it doesn't need to be the root element.
  • This is a very simple implementation without support for PostCSS. The CSS code is injected as-it-is. To add PostCSS support, you can adapt the css function checking this example:
import postcss from 'postcss';
import autoprefixer from 'autoprefixer';

const cssStr = `
  body {
    display: flex;
  }
`;

postcss([autoprefixer({ browsers: ['last 2 versions'] } )])
  .process(cssStr, { from: undefined })
  .then((result) => {
    console.log(result.css);
  });

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment