@mochi-css/vanilla
v3.0.1
Published
This package is part of the [Mochi-CSS project](https://github.com/Niikelion/mochi-css). It provides type-safe CSS-in-JS styling functions with static extraction support, allowing you to write styles in TypeScript that get extracted to plain CSS at build
Maintainers
Readme
🧁 Mochi-CSS/vanilla
This package is part of the Mochi-CSS project. It provides type-safe CSS-in-JS styling functions with static extraction support, allowing you to write styles in TypeScript that get extracted to plain CSS at build time.
Installation
npm i @mochi-css/vanillaFunctions
css(...styles)
css is the fundamental styling function of Mochi-CSS.
It takes style definitions as parameters, marks them for static extraction and returns class names to apply on elements.
import {css} from "@mochi-css/vanilla"
const buttonStyles = css({
borderRadius: 10,
border: "2px solid red"
})
// Use in JSX
const button = <button className={buttonStyles}>Click me</button>Output of the css function is also a valid style definition, so you can split your styles:
const textStyles = css({
color: "green"
})
const buttonStyles = css(textStyles, {
borderRadius: 10,
border: "2px solid red"
})globalCss(styles)
globalCss injects styles into the global scope - they are not scoped to any class and apply to all matching elements.
Use it for resets, base typography, or any styles that must target plain HTML elements.
import { globalCss } from "@mochi-css/vanilla"
globalCss({
"*, *::before, *::after": { boxSizing: "border-box" },
body: { margin: 0, fontFamily: "sans-serif" },
"h1, h2, h3": { lineHeight: 1.2 },
})styled(component, ...styles)
styled creates a styled component by combining a base element or component with style definitions.
It automatically applies the generated class names and forwards variant props.
import { styled } from "@mochi-css/react"
const Button = styled("button", {
borderRadius: 10,
border: "2px solid red"
})keyframes(stops)
The keyframes function lets you define CSS animations using the same type-safe syntax as style definitions.
Define animation stops using from/to or percentage keys:
import { keyframes, css } from "@mochi-css/vanilla"
const fadeIn = keyframes({
from: { opacity: 0 },
to: { opacity: 1 }
})
const fadeInStyle = css({
animation: `${fadeIn} 0.3s ease`
})For more control over animation timing, use percentage-based stops:
const bounce = keyframes({
"0%": { transform: "translateY(0)" },
"50%": { transform: "translateY(-20px)" },
"100%": { transform: "translateY(0)" }
})Each stop can contain multiple CSS properties with auto-units:
const grow = keyframes({
from: {
opacity: 0,
transform: "scale(0.5)",
fontSize: 12 // becomes 12px
},
to: {
opacity: 1,
transform: "scale(1)",
fontSize: 24 // becomes 24px
}
})Style definitions
A style definition is either a bundle of styles returned by css, or an object containing:
- any number of valid CSS properties converted to camelCase, like in React's style property
- any number of CSS variable assignments (see Tokens)
- optional variants definition
- optional default variants definition
Nested Selectors
Mochi-CSS supports nested selectors, allowing you to define styles for child elements, pseudo-classes, and pseudo-elements directly within your style definitions.
The & character represents the parent selector and must be included in every nested selector:
import { css } from "@mochi-css/vanilla"
const buttonStyle = css({
backgroundColor: "blue",
color: "white",
// Pseudo-classes
"&:hover": {
backgroundColor: "darkblue"
},
"&:active": {
backgroundColor: "navy"
},
"&:disabled": {
backgroundColor: "gray",
cursor: "not-allowed"
},
// Pseudo-elements
"&::before": {
content: '""',
display: "block"
},
// Child selectors
"& > span": {
fontWeight: "bold"
},
// Descendant selectors
"& p": {
margin: 0
},
// Compound selectors (when element also has another class)
"&.active": {
borderColor: "green"
},
// Adjacent sibling
"& + &": {
marginTop: 8
}
})Selector Position
The & can appear anywhere in the selector, allowing for flexible parent-child relationships:
const linkStyle = css({
color: "blue",
// Parent context: style this element when inside .dark-mode
".dark-mode &": {
color: "lightblue"
},
// Multiple parent contexts
"nav &, footer &": {
textDecoration: "underline"
}
})Media Selectors
Mochi-CSS supports @media, @container, @supports, and @layer at-rules as style object keys.
You can write them as plain strings or use the typed helper functions exported from the package.
media helper
import { css, media } from "@mochi-css/vanilla"
const responsiveContainer = css({
display: "flex",
flexDirection: "row",
padding: 32,
// media(condition) — wraps condition in parens automatically
[media("max-width: 768px")]: {
flexDirection: "column",
padding: 16
},
// Modern range syntax
[media("width <= 1024px")]: {
gap: 16
},
// Shorthand properties for common queries
[media.dark]: {
backgroundColor: "#1a1a1a",
color: "white"
},
[media.motion]: {
transition: "none"
},
[media.print]: {
display: "block"
}
})Combine conditions with media.and / media.or:
const layout = css({
display: "grid",
gridTemplateColumns: "1fr",
[media.and("min-width: 768px", "max-width: 1024px")]: {
gridTemplateColumns: "1fr 1fr",
gap: 16
},
[media.or("max-width: 480px", "print")]: {
display: "block"
}
})container helper
import { css, container } from "@mochi-css/vanilla"
const card = css({
fontSize: 16,
// Anonymous container query
[container("min-width: 400px")]: {
fontSize: 20
},
// Named container query
[container.named("sidebar", "min-width: 200px")]: {
display: "none"
}
})supports helper
import { css, supports } from "@mochi-css/vanilla"
const grid = css({
display: "flex",
[supports("display: grid")]: {
display: "grid"
},
[supports.not("display: grid")]: {
display: "flex"
},
[supports.and("display: grid", "gap: 1px")]: {
gap: 16
}
})Raw strings
You can also use raw at-rule strings directly without the helpers:
const styles = css({
"@media (max-width: 768px)": { padding: 8 },
"@container (min-width: 300px)": { fontSize: 20 },
"@supports (display: grid)": { display: "grid" },
"@layer utilities": { color: "red" }
})Combining Nested Selectors with Media Selectors
Nested selectors and media selectors can be combined for fine-grained control:
const buttonStyle = css({
backgroundColor: "blue",
"&:hover": {
backgroundColor: "darkblue",
// Media selector inside nested selector
[media("max-width: 480px")]: {
backgroundColor: "blue"
}
},
// Media selector with nested selectors inside
[media("max-width: 768px")]: {
padding: 8,
"& > span": {
display: "none"
}
}
})Using with Variants
Nested selectors and media selectors work seamlessly inside variant definitions:
const cardStyle = css({
padding: 16,
borderRadius: 8,
variants: {
size: {
small: {
padding: 8,
[media("max-width: 480px")]: {
padding: 4
}
},
large: {
padding: 32,
[media("max-width: 480px")]: {
padding: 16
}
}
},
interactive: {
true: {
cursor: "pointer",
"&:hover": {
transform: "translateY(-2px)"
}
},
false: {}
}
},
defaultVariants: {
size: "small",
interactive: false
}
})Variants
You may want to create multiple variants of a button that share a common base style:
import {css} from "@mochi-css/vanilla"
const baseButtonStyle = css({
border: "2px solid black",
color: "black",
backgroundColor: "white"
})
const redButtonStyle = css(baseButtonStyle, {
backgroundColor: "red"
})This works, but requires you to either manually select which style to apply in your component logic, or create separate components for each variant. Mochi-CSS allows you to define variants directly in your style definition and automatically generates the corresponding props for your component.
import { css } from "@mochi-css/vanilla"
import { styled } from "@mochi-css/react"
const buttonStyle = css({
border: "2px solid black",
color: "black",
backgroundColor: "white",
variants: {
color: {
white: {
backgroundColor: "white"
},
red: {
backgroundColor: "red"
}
}
},
defaultVariants: {
color: "white"
}
})
const Button = styled("button", buttonStyle)
const SomeComponent = () => <div>
<Button>White button</Button>
<Button color="red">Red button</Button>
</div>defaultVariants is optional,
but specifying defaults for all variants ensures predictable styling when variant props are omitted.
Tokens
Mochi-CSS provides typed wrappers around CSS variables to help with type safety. Tokens ensure that only valid values are assigned to your CSS variables.
Create tokens using the createToken<T>(name) function,
where T is a CSS value type like CssColorLike, CssLengthLike, or string:
import {createToken, css, CssColorLike} from "@mochi-css/vanilla"
const primaryColor = createToken<CssColorLike>("primaryColor")
const secondaryColor = createToken<CssColorLike>("secondaryColor")
const buttonColor = createToken<CssColorLike>("buttonColor")
const buttonStyle = css({
backgroundColor: buttonColor,
variants: {
variant: {
primary: {
[buttonColor.variable]: primaryColor
},
secondary: {
[buttonColor.variable]: secondaryColor
}
}
},
defaultVariants: {
variant: "primary"
}
})Tokens can be used in two ways:
- As values: Use the token directly (e.g.,
backgroundColor: buttonColor) to reference the CSS variable - As keys: Use
token.variable(e.g.,[buttonColor.variable]: primaryColor) to assign a value to the CSS variable
