English · Русский
Warning
Since the library is in active development, many developer-friendly and organizational features are not yet present, for example:
- Documentation website:
It takes great time to build a docs website for a multi-framework library, you know? - Documentation comments:
Unstable APIs may change frequently, so we would have to rewrite TypeDoc comments without a break. - Examples:
Projects in the examples directory are actively used during development, but they do not yet serve as actual example projects. - Developer workflow
Documentation · Report a bug · Request a feature
- 🧰 Multi-framework support
- 🎨 Design based on Material Design 3
- 🧱 Primitive and complex components
- 🧩 Modular structure
-
Add the Vanilla Extract plugin to the project
The library can be used in any project which supports Vanilla Extract. All available integrations can be found at "Bundler Integration". Here's a list of currently supported integrations (at the moment of writing):- Astro (via
@vanilla-extract/vite-plugin
) - esbuild (via
@vanilla-extract/esbuild-plugin
) - Gatsby (via
gatsby-plugin-vanilla-extract
) - Next.js (via
@vanilla-extract/next-plugin
) - Parcel (via
@vanilla-extract/parcel-transformer
) - Remix (via
@vanilla-extract/vite-plugin
) - Rollup (via
@vanilla-extract/rollup-plugin
) - Vite (via
@vanilla-extract/vite-plugin
) - Webpack (via
@vanilla-extract/webpack-plugin
)
- Astro (via
-
Add the Vanilla Extract core library (
@vanilla-extract/css
):npm
npm install @vanilla-extract/css
pnpm
pnpm add @vanilla-extract/css
Yarn
yarn add @vanilla-extract/css
Bun
bun add @vanilla-extract/css
-
Install star4's Vanilla Extract utility package (
@star4/vanilla-extract
):npm
npm install @star4/vanilla-extract
pnpm
pnpm add @star4/vanilla-extract
Yarn
yarn add @star4/vanilla-extract
Bun
bun add @star4/vanilla-extract
-
Install star4's framework package
-
React (
@star4/react
):npm
npm install @star4/react
pnpm
pnpm add @star4/react
Yarn
yarn add @star4/react
Bun
bun add @star4/react
-
Solid (
@star4/solid
):npm
npm install @star4/solid
pnpm
pnpm add @star4/solid
Yarn
yarn add @star4/solid
Bun
bun add @star4/solid
-
In order to start using star4, you must first setup a theme. This can be done using the @star4/vanilla-extract
package.
A theme object can be created in an ordinary (non .css.*
) file using the createTheme
helper function:
import { createTheme } from "@star4/vanilla-extract";
export const { contract, theme } = createTheme({/* ... */});
This function accepts an options argument, which is an object specifying theme configuration. Some of its fields are required. Here's a minimalist example:
import { createTheme } from "@star4/vanilla-extract";
export const { contract, theme } = createTheme({
// Use a static color scheme
color: {},
// Specify font families for different font styles
typeface: {
plain: "system-ui",
brand: "sans-serif",
},
// Specify the font family used for Material Symbols icons
component: {
materialSymbol: {
font: "Material Symbols Outlined",
},
},
});
The createTheme
helper returns an object containing two functions: contract
and theme
.
First, create a theme contract using the contract
helper:
// THIS EXAMPLES ASSUMES:
// - a star4 theme was created
// - `contract` was imported into this file
export const THEME = contract();
The theme
function returns an object filled with CSS values, ready to be applied using (@vanilla-extract/css
) inside of a .css.*
file. You can use any Vanilla Extract API, depending on your needs.
Example creating a theme which follows system preference, using globalStyle
and assignVars
APIs:
// THIS EXAMPLES ASSUMES:
// - a star4 theme was created
// - a theme contract named `THEME` was created
// - `theme` helper and `THEME` were imported into this file
import { assignVars, globalStyle } from "@vanilla-extract/css";
const LIGHT_THEME = theme("light");
const DARK_THEME = theme("dark");
globalStyle(
":root, ::backdrop",
{
colorScheme: "light dark",
"@media": {
"(prefers-color-scheme: light)": {
vars: assignVars(THEME, LIGHT_THEME),
},
"(prefers-color-scheme: dark)": {
vars: assignVars(THEME, DARK_THEME),
},
},
},
);
Here is a more complicated example, allowing overriding the current theme mode using a data attribute:
// THIS EXAMPLES ASSUMES:
// - a star4 theme was created
// - a theme contract named `THEME` was created
// - `theme` helper and `THEME` were imported into this file
import { assignVars, globalStyle } from "@vanilla-extract/css";
// A helper function for creating attribute selectors
const createThemeSelector = (
themes: string | string[],
not: boolean = false,
) => {
const values = typeof themes === "string" ? [themes] : themes;
const attributes = values.map(
theme => `[data-theme="${theme}"]`,
);
let is = `:is(${attributes.join(",")})`;
if(not) is = `:not(${is})`;
return `:root${is}, ${is} ::backdrop`;
}
// Store filled theme objects for reuse
const LIGHT_THEME = theme("light");
const DARK_THEME = theme("dark");
// Possible data-theme attribute values
const DATA_THEME_LIGHT = "light";
const DATA_THEME_DARK = "dark";
// Follow system theme mode if no
// [data-theme="light"] or [data-theme="dark"]
// attribute was found
globalStyle(
createThemeSelector([DATA_THEME_LIGHT, DATA_THEME_DARK], true),
{
colorScheme: "light dark",
"@media": {
"(prefers-color-scheme: light)": {
vars: assignVars(THEME, LIGHT_THEME),
},
"(prefers-color-scheme: dark)": {
vars: assignVars(THEME, DARK_THEME),
},
},
},
);
// If <html> has a [data-theme="light"]
// attribute, apply the light theme
globalStyle(
createThemeSelector(DATA_THEME_LIGHT),
{
colorScheme: "light",
vars: assignVars(THEME, LIGHT_THEME),
}
);
// If <html> has a [data-theme="dark"]
// attribute, apply the dark theme
globalStyle(
createThemeSelector(DATA_THEME_DARK),
{
colorScheme: "dark",
vars: assignVars(THEME, DARK_THEME),
}
);
star4 uses the Material You color system, which provides two options for creating color schemes: static and dynamic. See Choosing a scheme for advice.
Warning
The behaviour of the color
property might change in the future
The color
property is responsible for color scheme configuration. Settings it to an empty object {}
will assert a static scheme, while creating a dynamic scheme requires specifying a few properties.
Example of a static color scheme:
export const { contract, theme } = createTheme({
// Empty object - using a static color scheme
color: {},
/* ... other fields... */
});
Example of a dynamic color scheme:
export const { contract, theme } = createTheme({
color: {
// A total of 9 variants are available
variant: "tonalSpot"
// Can be a hex, rgb CSS color, or an Hct instance
sourceColor: "#00ff00",
// Defaults to 0. Allows changing color scheme contrast
contrastLevel: 0,
},
/* ... other fields... */
});
Font family names containing spaces must be quoted in CSS.
Here is a helper function for merging CSS font names:
type FontFamily = (string | FontFamily)[];
const fontFamily = (...args: FontFamily): string => {
return args
.map(
value => typeof value === "string"
? value.includes(" ")
? `"${value}"`
: value
: fontFamily(...value),
)
.join(", ");
}
If using variable fonts from FontSource, their font family names have Variable
appended to them, e.g. Open Sans Variable
, so here is a helper for FontSource fonts:
// Use system font if available,
// otherwise load the FontSource font
const fontSource = (family: string) => {
return [`${family}`, `${family} Variable`];
};
Here's an example showcasing both helpers in action:
// Gets transformed to:
// "Roboto Flex", "Roboto Flex Variable", "Open Sans", "Open Sans Variable", Roboto, system-ui, Arial, sans-serif
const TYPEFACE_PLAIN = fontFamily(
fontSource("Roboto Flex"),
fontSource("Open Sans"),
"Roboto",
"system-ui",
"Arial",
"sans-serif",
);
// Gets transformed to:
// Raleway, "Raleway Variable", Manrope, "Manrope Variable", sans-serif
const TYPEFACE_BRAND = fontFamily(
fontSource("Raleway"),
fontSource("Manrope"),
"sans-serif",
);
export const { contract, theme } = createTheme({
typeface: {
plain: TYPEFACE_PLAIN,
brand: TYPEFACE_BRAND,
},
/* other fields */
});
star4 framework packages export all available components, most of them contain documentation comments and code samples, so documentation for each component will not be provided here.
The @star4/react
package provides an additional helper with each component: .is()
. This is a function which can be used to identify if a React element is a specific element. It is useful for filtering out children elements:
import { Children, type ReactNode } from "react";
import { Button } from "@star4/react";
// This component only displays buttons
// Warns when other elements are passed as children
export function OnlyButtons({ children }: { children: ReactNode }) {
const buttons = Children.toArray(children)
.filter(node => {
const is = Button.is(node);
if(!is) console.warn(
"Invalid JSX Element passed to token resolver:",
node,
);
return is;
});
return buttons;
}
Warning
Name and return value of the createIdentifiableElement
function might change in the future.
Making your own components identifiable is also possible via the createIdentifiableElement
helper. We advice you to follow the naming convention shown in the example:
import { createIdentifiableElement } from "@star4/react";
// forwardRef may also be used instead of direct assignment
const ExampleComponent = function Example() {
}
export const Example = Object.assign(
// memo(ExampleComponent) may also be used for memoization
ExampleComponent,
// IS_EXAMPLE is the description of
// a Symbol created to identify the element
createIdentifiableElement("IS_EXAMPLE"),
);
// Use the component
<Example />
// Identify the element
Example.is(something)
star4 was only possible because of the following amazing projects:
- Solid Primitives - GitHub
The swiss army knife for SolidJS.
-
Material Web - GitHub
Official Material Design 3 web components library. -
usehooks-ts - GitHub
Awesome React hooks library.
See CONTRIBUTING.md.
This project is licensed under the MIT License.