🎩 Flairup

A CSS-in-JS library for UI package authors

Introduction

What is Flairup?

Flairup is a CSS-in-JS library specifically designed for UI package authors. Unlike general CSS-in-JS solutions, Flairup focuses on solving the unique challenges of distributing reusable UI components with their styles.

The Challenge

When creating third-party packages, you face different challenges than when building applications. Most existing styling solutions are designed for applications, not for packages meant to be shared and consumed by others.

Common Problems

  • Manual Style Imports: Users often need to manually import CSS files or configure style loaders, creating friction in the adoption process.
  • Bundler Configuration: Different bundlers (Webpack, Rollup, Vite) require different configurations for handling styles, making it hard to create universally compatible packages.
  • Style Conflicts: When multiple packages use the same class names or CSS variables, styles can conflict and override each other unexpectedly.
  • SSR Challenges: Server-side rendering often requires special handling for styles, with solutions varying between frameworks and environments.

Flairup's Solution

  • Requiring zero configuration from package consumers
  • Working seamlessly with all bundlers and environments
  • Automatically scoping styles to prevent conflicts
  • Providing built-in SSR support
  • Optimizing performance with one class per CSS property

Battle-tested on Emoji-Picker-React, Flairup makes it easy to ship styles with your components while ensuring they work reliably in any environment.

Core Concepts

The StyleSheet Singleton

At the heart of FlairUp is the StyleSheet object, which serves as a singleton for your entire package. This means that all styles across your library share the same stylesheet instance, allowing for efficient style deduplication and management.

The StyleSheet is created once using the `createSheet` function and can be used throughout your package. All styles defined using this stylesheet will be automatically deduplicated, ensuring optimal performance and minimal CSS output.

1import { createSheet } from 'flairup';
2
3// Create a stylesheet for your package
4const stylesheet = createSheet('MyPackageName');
5
6// Use the stylesheet to create styles
7const styles = stylesheet.create({
8 button: {
9 color: 'red',
10 ':hover': {
11 color: 'blue',
12 },
13 },
14});

One Class Per Property

FlairUp optimizes performance by generating a single class for each unique CSS property value. This means that if the same style is used in multiple places, it will only be added to the stylesheet once.

For example, if you use the color 'red' in multiple components, FlairUp will create a single class for it and reuse it across all instances, reducing the overall CSS bundle size.

Style Tag Injection

FlairUp works by injecting a single <style> tag into the DOM. This tag contains all the styles for your package, and it is automatically managed by FlairUp.

During server-side rendering, FlairUp ensures that the styles are properly injected into the HTML, and on the client side, it manages the style tag to prevent duplicate injections.

Features

Simple API

Easy to use with a familiar CSS-like syntax

TypeScript Support

Full TypeScript support for better development experience

Scoped Styles

Automatic style scoping to prevent conflicts

SSR Ready

Built-in support for server-side rendering

Installation

1npm install flairup
2# or
3yarn add flairup

Basic Usage

Basic Usage

The most basic usage of FlairUp, demonstrating how to create and apply styles to a component. This example shows a simple button with hover effects using the `create` function to define styles and the `cx` function to apply them.

Styles

1const styles = sheet.create({
2 "button": {
3 "color": "blue",
4 "backgroundColor": "white",
5 "padding": "10px 20px",
6 "border": "1px solid blue",
7 "borderRadius": "5px",
8 "cursor": "pointer",
9 ":hover": {
10 "backgroundColor": "lightblue",
11 "borderColor": "darkblue"
12 }
13 }
14});

Usage

1function MyButton() {
2 return (
3 <button className={cx(styles.button)}>
4 Hover me!
5 </button>
6 );
7}

Styling Variants and Scopes

Styling Variants and Scopes

Demonstrates FlairUp's ability to manage multiple, scoped styles within a single stylesheet. This example showcases a button group with different visual variants (default, primary, and danger), all defined within the same `styles` object. The `create` function is used to define these styles, and the `cx` function facilitates their application, allowing for straightforward composition of different style scopes to a single element.

Styles

1const styles = sheet.create({
2 "button": {
3 "backgroundColor": "#3498db",
4 "color": "white",
5 "padding": "10px 20px",
6 "borderRadius": "5px",
7 "border": "none",
8 "cursor": "pointer",
9 "&:hover": {
10 "backgroundColor": "#2980b9"
11 }
12 },
13 "primary": {
14 "backgroundColor": "#2ecc71",
15 "&:hover": {
16 "backgroundColor": "#27ae60"
17 }
18 },
19 "danger": {
20 "backgroundColor": "#e74c3c",
21 "&:hover": {
22 "backgroundColor": "#c0392b"
23 }
24 }
25});

Usage

1function ButtonGroup() {
2 return (
3 <div className={cx(styles.buttonGroup)}>
4 <button className={cx(styles.button)}>Default</button>
5 <button className={cx(styles.button, styles.primary)}>Primary</button>
6 <button className={cx(styles.button, styles.danger)}>Danger</button>
7 </div>
8 );
9}

CSS Variables

CSS Variables

Shows how to use CSS variables in FlairUp. Unlike regular CSS properties, CSS variables are added as a single class per scope. This example demonstrates how to define and use CSS variables to create themeable components with different color variants.

Default Box
Primary Box
Secondary Box

Styles

1const styles = sheet.create({
2 "box": {
3 "--box-bg-color": "lightgreen",
4 "backgroundColor": "var(--box-bg-color)",
5 "padding": "15px",
6 "borderRadius": "8px",
7 "margin": "10px 0"
8 },
9 "box--primary": {
10 "--box-bg-color": "lightblue"
11 },
12 "box--secondary": {
13 "--box-bg-color": "lightcoral"
14 },
15 "button": {
16 "--": {
17 "--button-bg": "#3498db",
18 "--button-hover-bg": "#2980b9",
19 "--button-text": "white",
20 "--button-padding": "10px 20px",
21 "--button-radius": "4px"
22 },
23 "backgroundColor": "var(--button-bg)",
24 "color": "var(--button-text)",
25 "padding": "var(--button-padding)",
26 "borderRadius": "var(--button-radius)",
27 "border": "none",
28 "cursor": "pointer",
29 "transition": "background-color 0.3s ease",
30 ":hover": {
31 "backgroundColor": "var(--button-hover-bg)"
32 }
33 },
34 "button--danger": {
35 "--": {
36 "--button-bg": "#e74c3c",
37 "--button-hover-bg": "#c0392b"
38 }
39 },
40 "button--success": {
41 "--": {
42 "--button-bg": "#2ecc71",
43 "--button-hover-bg": "#27ae60"
44 }
45 }
46});

Usage

1function Boxes() {
2 return (
3 <>
4 <div className={cx(styles.box)}>Default Box</div>
5 <div className={cx(styles.box, styles['box--primary'])}>Primary Box</div>
6 <div className={cx(styles.box, styles['box--secondary'])}>Secondary Box</div>
7 </>
8 );
9}

Media Queries

Media Queries

Demonstrates how to use media queries in FlairUp. This example shows a responsive box that changes its background color and padding based on the viewport width. The media query is defined directly in the style object using the `@media` syntax.

Resize the window to see the effect

Styles

1const styles = sheet.create({
2 "box": {
3 "backgroundColor": "lightblue",
4 "padding": "20px",
5 "@media (max-width: 600px)": {
6 "backgroundColor": "lightcoral",
7 "padding": "10px"
8 }
9 }
10});

Usage

1function ResponsiveBox() {
2 return (
3 <div className={cx(styles.box)}>
4 Resize the window to see the effect
5 </div>
6 );
7}

Pseudo Selectors & Elements

Pseudo Selectors & Elements

Demonstrates the use of pseudo-selectors and pseudo-elements in FlairUp. This example shows a button with hover, active, and focus states, as well as before and after pseudo-elements. The styles are defined using the standard CSS pseudo-selector syntax within the style object.

Styles

1const styles = sheet.create({
2 "button": {
3 "backgroundColor": "#f1c40f",
4 "color": "white",
5 "padding": "10px 20px",
6 "borderRadius": "5px",
7 "border": "none",
8 "cursor": "pointer",
9 "position": "relative",
10 "transition": "all 0.3s ease",
11 ":hover": {
12 "backgroundColor": "#f39c12",
13 "transform": "translateY(-2px)"
14 },
15 ":active": {
16 "transform": "translateY(0)"
17 },
18 ":focus": {
19 "outline": "none",
20 "boxShadow": "0 0 0 3px rgba(241, 196, 15, 0.4)"
21 },
22 "::before": {
23 "content": "🎩",
24 "position": "absolute",
25 "left": "10px",
26 "top": "50%",
27 "transform": "translateY(-50%)"
28 },
29 "::after": {
30 "content": "→",
31 "position": "absolute",
32 "right": "10px",
33 "top": "50%",
34 "transform": "translateY(-50%)"
35 }
36 }
37});

Usage

1function FancyButton() {
2 return (
3 <button className={cx(styles.button)}>
4 Hover me!
5 </button>
6 );
7}

Parent Class Support

Parent Class Support

FlairUp allows you to scope styles based on a top-level class name provided when defining your styles. This feature serves two key purposes:
  • Theme Responsiveness within your Component: By defining styles under a specific parent class, your component's styles can react to external theming or global styles applied at a higher level in the application. For example, you can have different styles for your component when it resides within a `.theme-dark` container.
  • User Customization: This feature enables users consuming your component to easily customize its appearance by applying their own top-level classes. Your component can then define specific styles that are activated when these user-defined classes are present in the component's parent hierarchy. This example demonstrates theme-based styling where button styles change based on the parent theme class (dark or light). The styles are scoped using the `.theme-dark` and `.theme-light` selectors, allowing for contextual styling

Styles

1const styles = sheet.create({
2 ".theme-dark": {
3 "button": {
4 "backgroundColor": "#3498db",
5 "color": "white",
6 "&:hover": {
7 "backgroundColor": "#2980b9"
8 }
9 }
10 },
11 ".theme-light": {
12 "button": {
13 "backgroundColor": "#2ecc71",
14 "color": "white",
15 "&:hover": {
16 "backgroundColor": "#27ae60"
17 }
18 }
19 }
20});

Usage

1function ThemeButtons() {
2 return (
3 <div className={cx(styles.themeContainer)}>
4 <div className={cx(styles.themeBox, styles.themeDark, 'theme-dark')}>
5 <button className={cx(styles.button)}>Dark Theme Button</button>
6 </div>
7 <div className={cx(styles.themeBox, styles.themeLight, 'theme-light')}>
8 <button className={cx(styles.button)}>Light Theme Button</button>
9 </div>
10 </div>
11 );
12}

Keyframes Animations

Keyframes Animations

Demonstrates how to create and use keyframe animations in FlairUp. This example shows two animated boxes: one using a bounce animation and another using a pulse animation. The keyframes are defined using the `keyframes` function and applied to elements using the `animation` property.

🎩
🎩

Styles

1// First, define your keyframes
2const keyframes = stylesheet.keyframes({
3 bounce: {
4 '0%': { transform: 'translateY(0)' },
5 '50%': { transform: 'translateY(-20px)' },
6 '100%': { transform: 'translateY(0)' },
7 },
8 pulse: {
9 '0%': { transform: 'scale(1)' },
10 '50%': { transform: 'scale(1.2)' },
11 '100%': { transform: 'scale(1)' },
12 },
13});
14
15// Then use them in your styles with template strings
16const styles = stylesheet.create({
17 box: {
18 width: '50px',
19 height: '50px',
20 backgroundColor: '#3498db',
21 margin: '10px',
22 display: 'flex',
23 justifyContent: 'center',
24 alignItems: 'center',
25 fontSize: '30px',
26 lineHeight: '1',
27 borderRadius: '10px',
28 },
29 bouncingBox: {
30 animation: `${keyframes.bounce} 1s infinite`,
31 },
32 pulsingBox: {
33 animation: `${keyframes.pulse} 1s infinite`,
34 },
35});

Usage

1// Finally, apply the styles
2function AnimatedBoxes() {
3 return (
4 <div className={cx(styles.container)}>
5 <div className={cx(styles.box, styles.bouncingBox)}>🎩</div>
6 <div className={cx(styles.box, styles.pulsingBox)}>🎩</div>
7 </div>
8 );
9}