@salty-css/vite 0.1.0-alpha.35 → 0.1.0-alpha.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +321 -551
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,458 +1,357 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
-
# Salty CSS
|
|
3
|
+
# Salty CSS — CSS-in-JS that compiles away
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Salty CSS is a build-time, type-safe CSS-in-JS library. You author styles in `.css.ts` files; the compiler emits real CSS, your runtime ships zero styling logic.
|
|
6
6
|
|
|
7
|
-
[Get started](#get-started)
|
|
7
|
+
[Get started](#get-started) · [API index](#api-index) · [Docs](https://salty-css.dev/docs) · [Discord](https://discord.gg/R6kr4KxMhP) · [GitHub](https://github.com/margarita-form/salty-css) · [NPM](https://www.npmjs.com/package/@salty-css/core)
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- Build
|
|
12
|
-
- Next.js, React Server Components,
|
|
13
|
-
- Type safety
|
|
14
|
-
-
|
|
15
|
-
-
|
|
11
|
+
- **Build-time compilation** — no runtime style injection, no FOUC, no client bundle cost.
|
|
12
|
+
- **Framework support** — Next.js (App + Pages, React Server Components, Webpack & Turbopack), React + Vite, React + Webpack, Astro.
|
|
13
|
+
- **Type safety** — TypeScript-first authoring, generated token types, an [ESLint plugin](https://www.npmjs.com/package/@salty-css/eslint-plugin-core).
|
|
14
|
+
- **Design tokens & theming** — static, responsive (media-bound), and conditional (data-attribute / class-bound) variables in one place.
|
|
15
|
+
- **Templates** — reusable style bundles with their own variants.
|
|
16
|
+
- **Modifiers** — custom value transformers (`'space:3'` → `'12px'`) defined in config.
|
|
17
|
+
- **Variants, compound variants, anyOf variants, default variants** out of the box.
|
|
16
18
|
|
|
17
19
|
## Get started
|
|
18
20
|
|
|
19
|
-
Fastest way to get started with any framework is
|
|
20
|
-
|
|
21
21
|
```bash
|
|
22
22
|
npx salty-css init
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
`init` detects your framework, installs the right packages, creates `salty.config.ts`, and wires the bundler plugin. Per-framework setup:
|
|
26
26
|
|
|
27
|
-
- Next.js → [
|
|
28
|
-
- React + Vite → [
|
|
29
|
-
-
|
|
27
|
+
- **Next.js** → [salty-css.dev/docs/installation](https://salty-css.dev/docs/installation) — `withSaltyCss(nextConfig)` in `next.config.ts`. Auto-detects Webpack vs Turbopack; React Server Components supported.
|
|
28
|
+
- **React + Vite** → [salty-css.dev/docs/installation](https://salty-css.dev/docs/installation) — `saltyPlugin(__dirname)` in `vite.config.ts`.
|
|
29
|
+
- **Astro** → [salty-css.dev/docs/installation](https://salty-css.dev/docs/installation) — `saltyIntegration()` in `astro.config.mjs`.
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
React + Webpack (without Next.js) is also supported via `@salty-css/webpack`.
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
- Build: `npx salty-css build [directory]`
|
|
35
|
-
- Update Salty CSS packages: `npx salty-css up`
|
|
33
|
+
## CLI
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
| Command | Alias | What it does |
|
|
36
|
+
| ----------------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------ |
|
|
37
|
+
| `npx salty-css init [directory]` | — | Detect framework, install packages, create `salty.config.ts`, wire the plugin. |
|
|
38
|
+
| `npx salty-css generate [filePath]` | `g` | Scaffold a new Salty component file. Flags: `--name`, `--className`, `--tag`. |
|
|
39
|
+
| `npx salty-css build [directory]` | `b` | Compile `*.css.ts` files into `saltygen/index.css`. Not needed when the bundler plugin is running. Flags: `--watch`, `--mode`. |
|
|
40
|
+
| `npx salty-css update [version]` | `up` | Update all `@salty-css/*` packages. Defaults to `latest`. Flags: `--dir`, `--yes`. |
|
|
41
|
+
| `npx salty-css --version` | — | Print CLI version. |
|
|
38
42
|
|
|
39
|
-
|
|
40
|
-
2. Salty CSS components created with styled function can extend non Salty CSS components (`export const CustomLink = styled(NextJSLink, { ... });`) but those components must take in `className` prop for styles to apply.
|
|
41
|
-
3. Among common types like `string` and `number`, CSS-in-JS properties in Salty CSS do support `functions` and `promises` as values (`styled('span', { base: { color: async () => 'red' } });`) but running asynchronous tasks or importing heavy 3rd party libraries into `*.css.ts` or `*.css.tsx` files can cause longer build times.
|
|
43
|
+
Full reference: [salty-css.dev/docs/cli](https://salty-css.dev/docs/cli).
|
|
42
44
|
|
|
43
|
-
##
|
|
45
|
+
## Good to know
|
|
44
46
|
|
|
45
|
-
|
|
47
|
+
1. **File extensions matter.** `styled`, `className`, `keyframes`, and every `defineX(...)` call must live in a file ending `.css.ts`, `.css.tsx`, `.salty.ts`, `.styles.ts`, or `.styled.ts`. The compiler ignores everything else.
|
|
48
|
+
2. **Extending non-Salty components is fine** — `styled(NextLink, { ... })` — as long as the wrapped component forwards `className` to a DOM element.
|
|
49
|
+
3. **Async values & functions are allowed** (`base: { color: async () => 'red' }`), but heavy imports inside `*.css.ts` slow the build.
|
|
50
|
+
4. **React Server Components are supported** via `@salty-css/next` — no `'use client'` needed for styled output.
|
|
46
51
|
|
|
47
|
-
##
|
|
52
|
+
## ESLint
|
|
48
53
|
|
|
49
|
-
|
|
54
|
+
Salty CSS ships a small ESLint plugin and matching shareable config. Two rules, both `error` by default, both autofixable, both scoped to Salty files (`.css.ts`, `.css.tsx`, `.salty.ts`, `.styles.ts`, `.styled.ts`):
|
|
50
55
|
|
|
51
|
-
-
|
|
52
|
-
-
|
|
56
|
+
- **`@salty-css/core/must-be-exported`** — every `styled`, `className`, `keyframes`, and `defineX*` call must be exported; the compiler ignores anything else.
|
|
57
|
+
- **`@salty-css/core/no-variants-in-base`** — `variants` must be a sibling of `base`, not nested inside it.
|
|
53
58
|
|
|
54
|
-
|
|
59
|
+
```bash
|
|
60
|
+
npm i -D @salty-css/eslint-plugin-core @salty-css/eslint-config-core
|
|
61
|
+
```
|
|
55
62
|
|
|
56
|
-
|
|
57
|
-
- [defineVariables](#variables) - create CSS variables (tokens) that can be used in any styling function
|
|
58
|
-
- [defineMediaQuery](#media-queries) - create CSS media queries and use them in any styling function
|
|
59
|
-
- [defineTemplates](#templates) - create reusable templates that can be applied when same styles are used over and over again
|
|
60
|
-
- [defineFont](#custom-fonts) - register custom fonts via `@font-face` (or a remote stylesheet) and expose them as a CSS variable
|
|
61
|
-
- [defineImport](#importing-additional-css) - pull in external CSS files (relative, public, node_modules, or URL)
|
|
62
|
-
- [keyframes](#keyframes-animations) - create CSS keyframes animation that can be used and imported in any styling function
|
|
63
|
+
**Flat config (ESLint 9+):**
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
```js
|
|
66
|
+
// eslint.config.mjs
|
|
67
|
+
import saltyConfig from '@salty-css/eslint-config-core/flat';
|
|
65
68
|
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
export default [saltyConfig];
|
|
70
|
+
```
|
|
68
71
|
|
|
69
|
-
|
|
72
|
+
**Legacy (`.eslintrc`):**
|
|
70
73
|
|
|
71
|
-
|
|
74
|
+
```js
|
|
75
|
+
module.exports = { extends: ['@salty-css/eslint-config-core'] };
|
|
76
|
+
```
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
Full reference → [salty-css.dev/docs/eslint](https://salty-css.dev/docs/eslint).
|
|
79
|
+
|
|
80
|
+
## API index
|
|
81
|
+
|
|
82
|
+
| Symbol | Import | One-liner | Docs |
|
|
83
|
+
| ----------------------------------------------- | ------------------------------ | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
|
84
|
+
| [`styled`](#styled) | `@salty-css/react/styled` | React component factory with variants & extension. | [api/styled](https://salty-css.dev/docs/api/styled) |
|
|
85
|
+
| [`className`](#classname) | `@salty-css/react/class-name` | CSS class string with `.variant()` chaining. | [api/classname](https://salty-css.dev/docs/api/classname) |
|
|
86
|
+
| [Variants](#variants) | — | `variants`, `compoundVariants`, `anyOfVariants`, `defaultVariants` semantics. | [variants](https://salty-css.dev/docs/variants) |
|
|
87
|
+
| [Overrides](#overrides) | — | Extend components, swap element, override per-instance via `style`. | [overrides](https://salty-css.dev/docs/overrides) |
|
|
88
|
+
| [ESLint plugin](#eslint) | `@salty-css/eslint-config-core` | Two rules: enforce `export` and correct `variants` placement on Salty files. | [eslint](https://salty-css.dev/docs/eslint) |
|
|
89
|
+
| [`defineConfig`](#defineconfig) | `@salty-css/core/config` | Top-level project config. | [api/config](https://salty-css.dev/docs/api/config) |
|
|
90
|
+
| [`defineVariables`](#definevariables) | `@salty-css/core/factories` | Static, responsive, and conditional CSS variables (tokens). | [variables](https://salty-css.dev/docs/variables) |
|
|
91
|
+
| [Theming](#theming) | — | `data-theme` switcher built on conditional variables. | [theming](https://salty-css.dev/docs/theming) |
|
|
92
|
+
| [`defineGlobalStyles`](#defineglobalstyles) | `@salty-css/core/factories` | Global rules (`html`, `body`, etc.). | [api/define-factories](https://salty-css.dev/docs/api/define-factories) |
|
|
93
|
+
| [`defineMediaQuery`](#definemediaquery) | `@salty-css/core/factories` | Named, reusable media queries. | [media-queries](https://salty-css.dev/docs/media-queries) |
|
|
94
|
+
| [`defineTemplates`](#definetemplates) | `@salty-css/core/factories` | Reusable style bundles, optionally with variants. | [templates](https://salty-css.dev/docs/templates) |
|
|
95
|
+
| [`defineFont`](#definefont) | `@salty-css/core/factories` | `@font-face` (or `@import`) + CSS variable in one. | [fonts](https://salty-css.dev/docs/fonts) |
|
|
96
|
+
| [`defineImport`](#defineimport) | `@salty-css/core/factories` | Pull external CSS into Salty's `imports` layer. | [imports](https://salty-css.dev/docs/imports) |
|
|
97
|
+
| [`keyframes`](#keyframes) | `@salty-css/react/keyframes` | Typed `@keyframes` with params and initial-state injection. | [animations](https://salty-css.dev/docs/animations) |
|
|
98
|
+
| [`defineViewportClamp`](#defineviewportclamp) | `@salty-css/core/helpers` | Fluid `clamp()` values that scale with the viewport. | [viewport-clamp](https://salty-css.dev/docs/viewport-clamp) |
|
|
99
|
+
| [`color`](#color) | `@salty-css/core/helpers` | Color manipulation (`alpha`, `darken`, …). | [color-function](https://salty-css.dev/docs/color-function) |
|
|
100
|
+
| [Modifiers](#modifiers) | (on `defineConfig`) | Custom value transformers, e.g. `'space:3'` → `'12px'`. | [modifiers](https://salty-css.dev/docs/modifiers) |
|
|
101
|
+
| [`withSaltyCss`](#withsaltycss-nextjs) | `@salty-css/next` | Next.js config wrapper (Webpack + Turbopack). | [installation](https://salty-css.dev/docs/installation) |
|
|
102
|
+
| [`saltyPlugin` (Vite)](#saltyplugin-vite) | `@salty-css/vite` | Vite plugin. | [installation](https://salty-css.dev/docs/installation) |
|
|
103
|
+
| [`saltyPlugin` (Webpack)](#saltyplugin-webpack) | `@salty-css/webpack` | Webpack loader + plugin. | [installation](https://salty-css.dev/docs/installation) |
|
|
104
|
+
| [`saltyIntegration`](#saltyintegration-astro) | `@salty-css/astro/integration` | Astro integration. | [installation](https://salty-css.dev/docs/installation) |
|
|
76
105
|
|
|
77
|
-
|
|
106
|
+
---
|
|
78
107
|
|
|
79
|
-
|
|
108
|
+
## `styled`
|
|
80
109
|
|
|
81
110
|
```ts
|
|
82
|
-
//
|
|
111
|
+
// components/button.css.ts
|
|
83
112
|
import { styled } from '@salty-css/react/styled';
|
|
84
113
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
element: 'section', // Override the html element that will be rendered for this component
|
|
114
|
+
export const Button = styled('button', {
|
|
115
|
+
className: 'btn', // optional custom class
|
|
116
|
+
element: 'button', // optional element override
|
|
89
117
|
base: {
|
|
90
|
-
|
|
118
|
+
padding: '0.6em 1.2em',
|
|
119
|
+
border: '1px solid currentColor',
|
|
120
|
+
'&:hover': { background: 'black', color: 'white' },
|
|
91
121
|
},
|
|
92
122
|
variants: {
|
|
93
|
-
|
|
123
|
+
variant: {
|
|
124
|
+
outlined: {},
|
|
125
|
+
solid: { background: 'black', color: 'white' },
|
|
126
|
+
},
|
|
94
127
|
},
|
|
95
128
|
compoundVariants: [
|
|
96
|
-
|
|
129
|
+
/* { variant: 'solid', size: 'lg', css: { ... } } */
|
|
97
130
|
],
|
|
98
|
-
defaultVariants: {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// Add additional default props for the component (eg, id and other html element attributes)
|
|
103
|
-
},
|
|
104
|
-
passProps: true, // Pass variant props to the rendered element / parent component (default: false)
|
|
105
|
-
priority: 1, // Override automatic priotity layer with a custom value (0-8), higher is considered more important
|
|
131
|
+
defaultVariants: { variant: 'outlined' },
|
|
132
|
+
defaultProps: { type: 'button' },
|
|
133
|
+
passProps: false, // pass variant props through to the rendered element
|
|
134
|
+
priority: undefined, // 0–8, higher wins; usually leave to auto
|
|
106
135
|
});
|
|
107
136
|
```
|
|
108
137
|
|
|
109
|
-
Example usage:
|
|
110
|
-
|
|
111
138
|
```tsx
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
export const Page = () => {
|
|
115
|
-
return <Component>Hello world</Component>;
|
|
116
|
-
};
|
|
139
|
+
<Button variant="solid">Save</Button>
|
|
117
140
|
```
|
|
118
141
|
|
|
119
|
-
|
|
142
|
+
See full reference → [salty-css.dev/docs/api/styled](https://salty-css.dev/docs/api/styled).
|
|
120
143
|
|
|
121
|
-
|
|
144
|
+
## `className`
|
|
145
|
+
|
|
146
|
+
Framework-agnostic class-string factory. The return value is a `string` you can drop in `className=`, plus a `.variant(name, value)` method for chaining variant classes.
|
|
122
147
|
|
|
123
148
|
```ts
|
|
124
|
-
//
|
|
149
|
+
// components/card.css.ts
|
|
125
150
|
import { className } from '@salty-css/react/class-name';
|
|
126
151
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
className: 'wrapper', // Define optional custom class name that will be included to the scope
|
|
152
|
+
export const card = className({
|
|
153
|
+
className: 'card',
|
|
130
154
|
base: {
|
|
131
|
-
|
|
155
|
+
padding: '1rem',
|
|
156
|
+
borderRadius: '8px',
|
|
157
|
+
},
|
|
158
|
+
variants: {
|
|
159
|
+
tone: {
|
|
160
|
+
neutral: { background: '#f6f6f6' },
|
|
161
|
+
brand: { background: '{colors.brand.main}' },
|
|
162
|
+
},
|
|
132
163
|
},
|
|
133
164
|
});
|
|
134
165
|
```
|
|
135
166
|
|
|
136
|
-
Example usage:
|
|
137
|
-
|
|
138
167
|
```tsx
|
|
139
|
-
|
|
168
|
+
<div className={`${card.variant('tone', 'brand')}`}>Hello</div>
|
|
169
|
+
```
|
|
140
170
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
171
|
+
See full reference → [salty-css.dev/docs/api/classname](https://salty-css.dev/docs/api/classname).
|
|
172
|
+
|
|
173
|
+
## Variants
|
|
174
|
+
|
|
175
|
+
`styled` and `className` share the same variant shape:
|
|
176
|
+
|
|
177
|
+
- **`variants`** — named axes (e.g. `size: { sm, md, lg }`). Each axis becomes a prop.
|
|
178
|
+
- **`compoundVariants`** — array; styles applied only when **all** listed axes match.
|
|
179
|
+
- **`anyOfVariants`** — array; styles applied when **any** listed axis matches.
|
|
180
|
+
- **`defaultVariants`** — values used when the prop is omitted at the call site.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
styled('a', {
|
|
184
|
+
base: { textDecoration: 'none' },
|
|
185
|
+
variants: {
|
|
186
|
+
size: { sm: { fontSize: 14 }, lg: { fontSize: 18 } },
|
|
187
|
+
underline: { true: { textDecoration: 'underline' } },
|
|
188
|
+
},
|
|
189
|
+
compoundVariants: [{ size: 'lg', underline: true, css: { textUnderlineOffset: 4 } }],
|
|
190
|
+
defaultVariants: { size: 'sm' },
|
|
191
|
+
});
|
|
144
192
|
```
|
|
145
193
|
|
|
146
|
-
|
|
194
|
+
Boolean variants accept `true`/`false` keys; pass them as `<Link underline />` shorthand. See [salty-css.dev/docs/variants](https://salty-css.dev/docs/variants).
|
|
195
|
+
|
|
196
|
+
## Overrides
|
|
197
|
+
|
|
198
|
+
- **Extend any component:** `styled(ExistingComponent, { ... })`. The wrapped component must accept `className`.
|
|
199
|
+
- **Swap the rendered element** for one instance: `<Button as="a" href="/x" />` (use `passProps` if you want variant props to reach the underlying element).
|
|
200
|
+
- **Per-instance style overrides:** `<Box style={{ ... }} />` and `<Box css={{ ... }} />` accept regular React `style` and Salty's `css` prop (overrides apply at the highest priority).
|
|
201
|
+
|
|
202
|
+
See [salty-css.dev/docs/overrides](https://salty-css.dev/docs/overrides) for `as`, `style`, CSS-variable overrides, and ref forwarding.
|
|
203
|
+
|
|
204
|
+
## `defineConfig`
|
|
147
205
|
|
|
148
206
|
```ts
|
|
149
|
-
//
|
|
150
|
-
import {
|
|
207
|
+
// salty.config.ts
|
|
208
|
+
import { defineConfig } from '@salty-css/core/config';
|
|
151
209
|
|
|
152
|
-
export
|
|
153
|
-
|
|
154
|
-
|
|
210
|
+
export const config = defineConfig({
|
|
211
|
+
importStrategy: 'root', // 'root' | 'component'
|
|
212
|
+
variables: {
|
|
213
|
+
/* see defineVariables */
|
|
214
|
+
},
|
|
215
|
+
global: {
|
|
216
|
+
/* see defineGlobalStyles */
|
|
217
|
+
},
|
|
218
|
+
templates: {
|
|
219
|
+
/* see defineTemplates */
|
|
155
220
|
},
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
margin: 0,
|
|
221
|
+
modifiers: {
|
|
222
|
+
/* see Modifiers */
|
|
159
223
|
},
|
|
160
|
-
//
|
|
224
|
+
reset: 'default', // 'default' | 'none' | GlobalStyles
|
|
225
|
+
externalModules: ['react', 'react-dom'],
|
|
226
|
+
strict: true, // true | 'warn' | false
|
|
227
|
+
defaultUnit: 'px', // px, rem, em, vh, vw, % …
|
|
161
228
|
});
|
|
162
229
|
```
|
|
163
230
|
|
|
164
|
-
|
|
231
|
+
Full reference → [salty-css.dev/docs/api/config](https://salty-css.dev/docs/api/config).
|
|
232
|
+
|
|
233
|
+
## `defineVariables`
|
|
234
|
+
|
|
235
|
+
Tokens come in three flavours — static, responsive (media-bound), conditional (class/data-attribute-bound):
|
|
165
236
|
|
|
166
237
|
```ts
|
|
167
|
-
//
|
|
238
|
+
// styles/variables.css.ts
|
|
168
239
|
import { defineVariables } from '@salty-css/core/factories';
|
|
169
240
|
|
|
170
241
|
export default defineVariables({
|
|
171
|
-
/*
|
|
172
|
-
Define static variable token (like colors, font sizes, etc.). and use them in your styles (e.g. color: '{colors.brand.highlight}').
|
|
173
|
-
Variables can be nested (colors.brand.main) and can reference other variables.
|
|
174
|
-
*/
|
|
175
242
|
colors: {
|
|
176
243
|
dark: '#111',
|
|
177
244
|
light: '#fefefe',
|
|
178
|
-
brand: {
|
|
179
|
-
main: '#0070f3',
|
|
180
|
-
highlight: '#ff4081',
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
fontFamily: {
|
|
184
|
-
heading: 'Arial, sans-serif',
|
|
185
|
-
body: 'Georgia, serif',
|
|
245
|
+
brand: { main: '#0070f3', highlight: '#ff4081' },
|
|
186
246
|
},
|
|
187
|
-
|
|
188
|
-
/*
|
|
189
|
-
Define variables that are responsive to a media query (defined in media.css.ts) asn use them in your styles as normal (e.g. font-size: '{fontSize.heading.regular}').
|
|
190
|
-
These variables will be automatically updated when the media query is matched. Base values are used when no media query is matched.
|
|
191
|
-
*/
|
|
192
247
|
responsive: {
|
|
193
|
-
base: {
|
|
194
|
-
|
|
195
|
-
heading: {
|
|
196
|
-
small: '32px',
|
|
197
|
-
regular: '48px',
|
|
198
|
-
large: '64px',
|
|
199
|
-
},
|
|
200
|
-
body: {
|
|
201
|
-
small: '16px',
|
|
202
|
-
regular: '20px',
|
|
203
|
-
large: '24px',
|
|
204
|
-
},
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
'@largeMobileDown': {
|
|
208
|
-
fontSize: {
|
|
209
|
-
heading: {
|
|
210
|
-
small: '20px',
|
|
211
|
-
regular: '32px',
|
|
212
|
-
large: '48px',
|
|
213
|
-
},
|
|
214
|
-
body: {
|
|
215
|
-
small: '14px',
|
|
216
|
-
regular: '16px',
|
|
217
|
-
large: '20px',
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
},
|
|
248
|
+
base: { fontSize: { heading: '48px', body: '16px' } },
|
|
249
|
+
'@largeMobileDown': { fontSize: { heading: '32px', body: '14px' } },
|
|
221
250
|
},
|
|
222
|
-
|
|
223
|
-
/*
|
|
224
|
-
Conditional variables are used to define styles that depend on a class name (e.g. <div className="theme-dark">). or data-attribute (e.g. <div data-theme="dark">). Names for these variables will be "{theme.backgroundColor}" and "{theme.textColor}".
|
|
225
|
-
*/
|
|
226
251
|
conditional: {
|
|
227
252
|
theme: {
|
|
228
|
-
dark: {
|
|
229
|
-
|
|
230
|
-
textColor: '{colors.light}',
|
|
231
|
-
},
|
|
232
|
-
light: {
|
|
233
|
-
backgroundColor: '{colors.light}',
|
|
234
|
-
textColor: '{colors.dark}',
|
|
235
|
-
},
|
|
253
|
+
dark: { backgroundColor: '{colors.dark}', textColor: '{colors.light}' },
|
|
254
|
+
light: { backgroundColor: '{colors.light}', textColor: '{colors.dark}' },
|
|
236
255
|
},
|
|
237
256
|
},
|
|
238
257
|
});
|
|
239
258
|
```
|
|
240
259
|
|
|
241
|
-
|
|
260
|
+
Use as string references: `{colors.brand.main}`, `{fontSize.heading}`, `{theme.textColor}`.
|
|
242
261
|
|
|
243
|
-
|
|
244
|
-
styled('span', {
|
|
245
|
-
base: {
|
|
246
|
-
// Use of static font family variable
|
|
247
|
-
fontFamily: '{fontFamily.heading}',
|
|
248
|
-
// Use of responsive font size variable
|
|
249
|
-
fontSize: '{fontSize.heading.regular}',
|
|
250
|
-
// Use of conditional theme text color variable
|
|
251
|
-
color: '{theme.textColor}',
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
```
|
|
262
|
+
See [salty-css.dev/docs/variables](https://salty-css.dev/docs/variables).
|
|
255
263
|
|
|
256
|
-
##
|
|
264
|
+
## Theming
|
|
257
265
|
|
|
258
|
-
|
|
266
|
+
The `conditional` bucket above wires up data-attribute / class-based themes with zero providers. Toggle a `data-theme` attribute on `<html>` (or any ancestor) and conditional variables resolve to the matching branch:
|
|
259
267
|
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
export const largePortraitUp = defineMediaQuery((media) => media.minWidth(600));
|
|
265
|
-
export const largeMobileDown = defineMediaQuery((media) => media.maxWidth(600));
|
|
268
|
+
```html
|
|
269
|
+
<html data-theme="dark">
|
|
270
|
+
<!-- '{theme.textColor}' resolves to '{colors.light}' -->
|
|
271
|
+
</html>
|
|
266
272
|
```
|
|
267
273
|
|
|
268
|
-
|
|
274
|
+
See the dark-mode walkthrough (with SSR flash fix) → [salty-css.dev/docs/theming](https://salty-css.dev/docs/theming).
|
|
275
|
+
|
|
276
|
+
## `defineGlobalStyles`
|
|
269
277
|
|
|
270
278
|
```ts
|
|
271
|
-
|
|
272
|
-
|
|
279
|
+
// styles/global.css.ts
|
|
280
|
+
import { defineGlobalStyles } from '@salty-css/core/factories';
|
|
273
281
|
|
|
274
|
-
|
|
282
|
+
export default defineGlobalStyles({
|
|
283
|
+
html: { fontFamily: 'Inter, system-ui, sans-serif' },
|
|
284
|
+
body: { margin: 0, background: '#fff' },
|
|
285
|
+
});
|
|
286
|
+
```
|
|
275
287
|
|
|
276
|
-
|
|
288
|
+
## `defineMediaQuery`
|
|
277
289
|
|
|
278
290
|
```ts
|
|
279
|
-
//
|
|
280
|
-
import {
|
|
291
|
+
// styles/media.css.ts
|
|
292
|
+
import { defineMediaQuery } from '@salty-css/core/factories';
|
|
281
293
|
|
|
282
|
-
export
|
|
283
|
-
|
|
284
|
-
textStyle: {
|
|
285
|
-
headline: {
|
|
286
|
-
small: {
|
|
287
|
-
fontSize: '{fontSize.heading.small}',
|
|
288
|
-
},
|
|
289
|
-
regular: {
|
|
290
|
-
fontSize: '{fontSize.heading.regular}',
|
|
291
|
-
},
|
|
292
|
-
large: {
|
|
293
|
-
fontSize: '{fontSize.heading.large}',
|
|
294
|
-
},
|
|
295
|
-
},
|
|
296
|
-
body: {
|
|
297
|
-
small: {
|
|
298
|
-
fontSize: '{fontSize.body.small}',
|
|
299
|
-
lineHeight: '1.5em',
|
|
300
|
-
},
|
|
301
|
-
regular: {
|
|
302
|
-
fontSize: '{fontSize.body.regular}',
|
|
303
|
-
lineHeight: '1.33em',
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
// Dynamic function templates for card styles.
|
|
308
|
-
card: (value: string) => {
|
|
309
|
-
return {
|
|
310
|
-
padding: value,
|
|
311
|
-
borderRadius: '8px',
|
|
312
|
-
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
|
|
313
|
-
};
|
|
314
|
-
},
|
|
315
|
-
});
|
|
294
|
+
export const largeMobileDown = defineMediaQuery((media) => media.maxWidth(600));
|
|
295
|
+
export const darkMode = defineMediaQuery((media) => media.prefersColorScheme('dark'));
|
|
316
296
|
```
|
|
317
297
|
|
|
318
|
-
Example usage:
|
|
319
|
-
|
|
320
298
|
```ts
|
|
321
|
-
styled('
|
|
299
|
+
styled('span', { base: { fontSize: 64, '@largeMobileDown': { fontSize: 32 } } });
|
|
322
300
|
```
|
|
323
301
|
|
|
324
|
-
|
|
302
|
+
See [salty-css.dev/docs/media-queries](https://salty-css.dev/docs/media-queries).
|
|
303
|
+
|
|
304
|
+
## `defineTemplates`
|
|
325
305
|
|
|
326
|
-
|
|
306
|
+
Reusable style bundles. Static templates pick values by dot-path; function templates take a parameter.
|
|
327
307
|
|
|
328
308
|
```ts
|
|
329
|
-
//
|
|
309
|
+
// styles/templates.css.ts
|
|
330
310
|
import { defineTemplates } from '@salty-css/core/factories';
|
|
331
311
|
|
|
332
312
|
export default defineTemplates({
|
|
333
313
|
textStyle: {
|
|
334
314
|
heading: {
|
|
335
|
-
|
|
336
|
-
base: {
|
|
337
|
-
fontFamily: '{fontFamily.heading}',
|
|
338
|
-
lineHeight: '1.1em',
|
|
339
|
-
},
|
|
315
|
+
base: { fontFamily: '{fontFamily.heading}', lineHeight: 1.1 },
|
|
340
316
|
variants: {
|
|
341
|
-
weight: {
|
|
342
|
-
light: { fontWeight: 300 },
|
|
343
|
-
regular: { fontWeight: 500 },
|
|
344
|
-
heavy: { fontWeight: 800 },
|
|
345
|
-
},
|
|
346
|
-
italic: {
|
|
347
|
-
true: { fontStyle: 'italic' },
|
|
348
|
-
},
|
|
317
|
+
weight: { regular: { fontWeight: 500 }, heavy: { fontWeight: 800 } },
|
|
349
318
|
},
|
|
350
|
-
|
|
351
|
-
weight: 'regular',
|
|
352
|
-
},
|
|
353
|
-
compoundVariants: [
|
|
354
|
-
// Applied when ALL listed axes match.
|
|
355
|
-
{ weight: 'heavy', italic: true, css: { letterSpacing: '-0.01em' } },
|
|
356
|
-
],
|
|
357
|
-
// Leaves can be plain styles…
|
|
319
|
+
large: { fontSize: '{fontSize.heading.large}' },
|
|
358
320
|
small: { fontSize: '{fontSize.heading.small}' },
|
|
359
|
-
regular: { fontSize: '{fontSize.heading.regular}' },
|
|
360
|
-
// …or rich, with their own additional variants / overrides.
|
|
361
|
-
large: {
|
|
362
|
-
base: { fontSize: '{fontSize.heading.large}' },
|
|
363
|
-
variants: {
|
|
364
|
-
weight: {
|
|
365
|
-
// Override the inherited bundle just for `large`.
|
|
366
|
-
heavy: { fontWeight: 900, letterSpacing: '-0.02em' },
|
|
367
|
-
},
|
|
368
|
-
},
|
|
369
|
-
},
|
|
370
321
|
},
|
|
371
322
|
},
|
|
323
|
+
card: (padding: string) => ({
|
|
324
|
+
padding,
|
|
325
|
+
borderRadius: 8,
|
|
326
|
+
boxShadow: '0 0 10px rgba(0,0,0,0.1)',
|
|
327
|
+
}),
|
|
372
328
|
});
|
|
373
329
|
```
|
|
374
330
|
|
|
375
|
-
Apply variants at the call site in either of two equivalent forms — string query or object:
|
|
376
|
-
|
|
377
331
|
```ts
|
|
378
|
-
styled('h1', {
|
|
379
|
-
base: {
|
|
380
|
-
// String form: `path@axis=value&axis=value&boolFlag`
|
|
381
|
-
textStyle: 'heading.large@weight=heavy&italic',
|
|
382
|
-
},
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
styled('h2', {
|
|
386
|
-
base: {
|
|
387
|
-
// Object form: `name` is the dot-path, the rest are axis values.
|
|
388
|
-
textStyle: { name: 'heading.large', weight: 'heavy', italic: true },
|
|
389
|
-
},
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// No variants — existing simple usage still works.
|
|
393
|
-
styled('p', { base: { textStyle: 'heading.regular' } });
|
|
332
|
+
styled('h1', { base: { textStyle: 'heading.large@weight=heavy', card: '20px' } });
|
|
394
333
|
```
|
|
395
334
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
- **Inheritance is parent → leaf only.** A leaf sees variants from its ancestors; siblings and children are invisible.
|
|
399
|
-
- **Closest wins.** If the same axis/value bundle is declared at multiple levels, the deepest one replaces (not merges) the ancestor's bundle for that single call.
|
|
400
|
-
- **`defaultVariants` apply when the call site omits an axis.** Walked bottom-up, same closest-wins rule.
|
|
401
|
-
- **`compoundVariants` (AND) and `anyOfVariants` (OR) are accumulated top-down** across the path — every matching rule contributes.
|
|
402
|
-
- **Boolean axes accept a shorthand.** `@italic` is equivalent to `@italic=true`; in object form pass `italic: true`.
|
|
403
|
-
- **Reserved keys** inside a rich node: `base`, `variants`, `defaultVariants`, `compoundVariants`, `anyOfVariants`. Don't use `name` as an axis (reserved for the object call-site form).
|
|
404
|
-
- **Function templates** (e.g. `card: (v) => ({ … })`) don't support variants — keep them as plain functions.
|
|
405
|
-
|
|
406
|
-
## Custom fonts
|
|
407
|
-
|
|
408
|
-
Register custom fonts that will be emitted as `@font-face` declarations and exposed as a CSS variable. Mirrors the developer experience of Next.js / Astro font loaders, but generated at build time alongside the rest of your Salty CSS output.
|
|
409
|
-
|
|
410
|
-
The returned object stringifies to its `font-family` value and exposes helpers for explicit usage:
|
|
335
|
+
Rich-node variants, inheritance, and the `compoundVariants`/`anyOfVariants` rules: [salty-css.dev/docs/templates](https://salty-css.dev/docs/templates).
|
|
411
336
|
|
|
412
|
-
|
|
413
|
-
- `Font.fontFamily` → final `font-family` string with fallbacks
|
|
414
|
-
- `Font.className` → class that sets the variable + applies the font on a subtree
|
|
415
|
-
- `Font.style` → object you can spread on a React `style` prop
|
|
337
|
+
## `defineFont`
|
|
416
338
|
|
|
417
339
|
```ts
|
|
418
|
-
//
|
|
340
|
+
// styles/fonts.css.ts
|
|
419
341
|
import { defineFont } from '@salty-css/core/factories';
|
|
420
342
|
|
|
421
|
-
// 1. Local or self-hosted @font-face sources.
|
|
422
|
-
// URLs are passed through as-is (use a public-folder path or a CDN URL).
|
|
423
343
|
export const Inter = defineFont({
|
|
424
|
-
name: 'Inter',
|
|
425
|
-
variable: '--font-inter',
|
|
426
|
-
display: 'swap',
|
|
427
|
-
fallback:
|
|
344
|
+
name: 'Inter',
|
|
345
|
+
variable: '--font-inter',
|
|
346
|
+
display: 'swap',
|
|
347
|
+
fallback: 'system-ui, sans-serif',
|
|
428
348
|
variants: [
|
|
429
|
-
{
|
|
430
|
-
|
|
431
|
-
style: 'normal',
|
|
432
|
-
// Shorthand: pass a string and the `format()` descriptor is auto-detected
|
|
433
|
-
// from the file extension (woff2, woff, ttf, otf, eot, svg, ttc).
|
|
434
|
-
src: '/fonts/inter-400.woff2',
|
|
435
|
-
},
|
|
436
|
-
{
|
|
437
|
-
weight: 700,
|
|
438
|
-
style: 'normal',
|
|
439
|
-
// Multiple sources can be a string array — first entry is preferred;
|
|
440
|
-
// the browser picks the first format it supports.
|
|
441
|
-
src: ['/fonts/inter-700.woff2', '/fonts/inter-700.ttf'],
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
weight: 400,
|
|
445
|
-
style: 'italic',
|
|
446
|
-
// Use the `{ url, format }` object form when the URL has no recognisable
|
|
447
|
-
// extension (signed CDN URLs, query-only endpoints, etc.). You can also
|
|
448
|
-
// mix strings and objects in the same array.
|
|
449
|
-
src: ['/fonts/inter-400-italic.woff2', { url: 'https://cdn.example.com/inter-italic', format: 'woff' }],
|
|
450
|
-
},
|
|
349
|
+
{ weight: 400, style: 'normal', src: '/fonts/inter-400.woff2' },
|
|
350
|
+
{ weight: 700, style: 'normal', src: ['/fonts/inter-700.woff2', '/fonts/inter-700.ttf'] },
|
|
451
351
|
],
|
|
452
352
|
});
|
|
453
353
|
|
|
454
|
-
//
|
|
455
|
-
// registers the CSS variable so usage stays the same as the @font-face flow.
|
|
354
|
+
// Or pull a remote stylesheet (e.g. Google Fonts) — emits @import + variable.
|
|
456
355
|
export const InterCdn = defineFont({
|
|
457
356
|
name: 'Inter',
|
|
458
357
|
variable: '--font-inter',
|
|
@@ -460,306 +359,177 @@ export const InterCdn = defineFont({
|
|
|
460
359
|
});
|
|
461
360
|
```
|
|
462
361
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
```tsx
|
|
466
|
-
import { Inter } from './fonts.css';
|
|
467
|
-
import { styled } from '@salty-css/react/styled';
|
|
468
|
-
|
|
469
|
-
// Apply the font globally by attaching its className high up in the tree.
|
|
470
|
-
// This sets `--font-inter` on the subtree and applies `font-family: var(--font-inter)`.
|
|
471
|
-
export const App = ({ children }) => <div className={Inter.className}>{children}</div>;
|
|
472
|
-
|
|
473
|
-
// `Inter` stringifies to its font-family value (with fallbacks), so it can be used directly.
|
|
474
|
-
export const Heading = styled('h1', {
|
|
475
|
-
base: {
|
|
476
|
-
fontFamily: `${Inter}`,
|
|
477
|
-
},
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
// Or reference the CSS variable explicitly.
|
|
481
|
-
export const Body = styled('p', {
|
|
482
|
-
base: {
|
|
483
|
-
fontFamily: `var(${Inter.variable})`,
|
|
484
|
-
},
|
|
485
|
-
});
|
|
486
|
-
```
|
|
362
|
+
`Inter.className`, `Inter.variable`, `Inter.fontFamily`, and `Inter.style` are available for explicit usage. See [salty-css.dev/docs/fonts](https://salty-css.dev/docs/fonts).
|
|
487
363
|
|
|
488
|
-
##
|
|
364
|
+
## `defineImport`
|
|
489
365
|
|
|
490
|
-
|
|
366
|
+
Pull external CSS into Salty's `imports` cascade layer (which sits **before** `reset`, `global`, `templates`, and your component layers — so your styles always win).
|
|
491
367
|
|
|
492
368
|
```ts
|
|
493
|
-
//
|
|
369
|
+
// styles/imports.css.ts
|
|
494
370
|
import { defineImport } from '@salty-css/core/factories';
|
|
495
371
|
|
|
496
372
|
export default defineImport(
|
|
497
|
-
//
|
|
498
|
-
'
|
|
499
|
-
//
|
|
500
|
-
'
|
|
501
|
-
//
|
|
502
|
-
'
|
|
503
|
-
|
|
504
|
-
'/fonts/inter.css',
|
|
505
|
-
// External URL
|
|
506
|
-
'https://fonts.googleapis.com/css2?family=Inter&display=swap',
|
|
507
|
-
// Object form — attach media or supports() conditions
|
|
508
|
-
{ url: './print.css', media: 'print' },
|
|
509
|
-
{ url: './p3.css', supports: 'color(display-p3 1 1 1)' }
|
|
373
|
+
'./reset.css', // relative
|
|
374
|
+
'modern-normalize/modern-normalize.css', // node_modules
|
|
375
|
+
'~normalize.css/normalize.css', // node_modules (~ form)
|
|
376
|
+
'/fonts/inter.css', // public/ folder
|
|
377
|
+
'https://fonts.googleapis.com/css2?family=Inter', // URL
|
|
378
|
+
{ url: './print.css', media: 'print' }, // media-conditional
|
|
379
|
+
{ url: './p3.css', supports: 'color(display-p3 1 1 1)' } // supports-conditional
|
|
510
380
|
);
|
|
511
381
|
```
|
|
512
382
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
| Pattern | Behaviour |
|
|
516
|
-
| --------------------------- | ---------------------------------------------------------------------------------------- |
|
|
517
|
-
| `http://`, `https://`, `//` | Emitted verbatim |
|
|
518
|
-
| Starts with `/` | Public-folder URL — emitted verbatim, the browser resolves it against your host |
|
|
519
|
-
| Starts with `./` or `../` | Resolved at build time relative to the file that called `defineImport` |
|
|
520
|
-
| `~package/file.css` | Stripped of the leading `~`, then resolved from `node_modules` and copied into the build |
|
|
521
|
-
| `package/file.css` (bare) | Same `node_modules` resolution as the `~` form |
|
|
522
|
-
|
|
523
|
-
All imports are placed inside a new `imports` cascade layer that sits **before** `reset`, `global`, `templates`, and your component styles. This means your own styles always win over third-party CSS you pull in — which is what most teams expect when they drop in something like `modern-normalize`.
|
|
383
|
+
Layer order: `@layer imports, reset, global, templates, fonts, l0…l8;`. See [salty-css.dev/docs/imports](https://salty-css.dev/docs/imports).
|
|
524
384
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
```css
|
|
528
|
-
@layer imports, reset, global, templates, l0, l1, l2, l3, l4, l5, l6, l7, l8;
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
## Keyframes animations
|
|
385
|
+
## `keyframes`
|
|
532
386
|
|
|
533
387
|
```ts
|
|
534
|
-
//
|
|
388
|
+
// styles/animations.css.ts
|
|
535
389
|
import { keyframes } from '@salty-css/react/keyframes';
|
|
536
390
|
|
|
537
391
|
export const fadeIn = keyframes({
|
|
538
|
-
// Name of the animation in final CSS
|
|
539
392
|
animationName: 'fadeIn',
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
delay: '250ms',
|
|
545
|
-
fillMode: 'forwards',
|
|
546
|
-
},
|
|
547
|
-
// Rest is animation timeline
|
|
548
|
-
from: {
|
|
549
|
-
opacity: 0,
|
|
550
|
-
},
|
|
551
|
-
to: {
|
|
552
|
-
opacity: 1,
|
|
553
|
-
},
|
|
393
|
+
appendInitialStyles: true, // injects `from`/`0%` as base styles on the component
|
|
394
|
+
params: { delay: '250ms', fillMode: 'forwards' },
|
|
395
|
+
from: { opacity: 0 },
|
|
396
|
+
to: { opacity: 1 },
|
|
554
397
|
});
|
|
555
398
|
```
|
|
556
399
|
|
|
557
|
-
Example usage:
|
|
558
|
-
|
|
559
400
|
```ts
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
export const Wrapper = styled('div', { base: { animation: fadeIn } });
|
|
401
|
+
styled('div', { base: { animation: fadeIn } });
|
|
563
402
|
```
|
|
564
403
|
|
|
565
|
-
|
|
404
|
+
See [salty-css.dev/docs/animations](https://salty-css.dev/docs/animations).
|
|
566
405
|
|
|
567
|
-
|
|
406
|
+
## `defineViewportClamp`
|
|
568
407
|
|
|
569
408
|
```ts
|
|
570
|
-
//
|
|
571
|
-
import { defineViewportClamp } from '@salty-css/
|
|
409
|
+
// styles/clamp.css.ts
|
|
410
|
+
import { defineViewportClamp } from '@salty-css/core/helpers';
|
|
572
411
|
|
|
573
412
|
export const fhdClamp = defineViewportClamp({ screenSize: 1920 });
|
|
574
|
-
export const mobileClamp = defineViewportClamp({ screenSize: 640 });
|
|
413
|
+
export const mobileClamp = defineViewportClamp({ screenSize: 640, axis: 'horizontal' });
|
|
575
414
|
```
|
|
576
415
|
|
|
577
|
-
Example usage:
|
|
578
|
-
|
|
579
416
|
```ts
|
|
580
417
|
styled('span', { base: { fontSize: fhdClamp(96), '@largeMobileDown': { fontSize: mobileClamp(48) } } });
|
|
581
418
|
```
|
|
582
419
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
Modify any color easily, add opacity, darken...
|
|
420
|
+
Options: `screenSize`, `axis` (`'horizontal' | 'vertical'`), `minMultiplier`, `maxMultiplier`, `minMaxUnit`. See [salty-css.dev/docs/viewport-clamp](https://salty-css.dev/docs/viewport-clamp).
|
|
586
421
|
|
|
587
|
-
|
|
422
|
+
## `color`
|
|
588
423
|
|
|
589
424
|
```ts
|
|
590
425
|
import { color } from '@salty-css/core/helpers';
|
|
426
|
+
import { styled } from '@salty-css/react/styled';
|
|
591
427
|
|
|
592
|
-
export const
|
|
428
|
+
export const Tint = styled('span', {
|
|
429
|
+
base: { backgroundColor: color('#000').alpha(0.5) },
|
|
430
|
+
});
|
|
593
431
|
```
|
|
594
432
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
### Next.js
|
|
598
|
-
|
|
599
|
-

|
|
600
|
-
|
|
601
|
-
Salty CSS provides Next.js App & Pages router support with full React Server Components support.
|
|
602
|
-
|
|
603
|
-
### Add Salty CSS to Next.js
|
|
604
|
-
|
|
605
|
-
1. In your existing Next.js repository you can run `npx salty-css init` to automatically configure Salty CSS.
|
|
606
|
-
2. Create your first Salty CSS component with `npx salty-css generate [filePath]` (e.g. src/custom-wrapper)
|
|
607
|
-
3. Import your component for example to `page.tsx` and see it working!
|
|
608
|
-
|
|
609
|
-
And note: steps 2 & 3 are just to show how get new components up and running, step 1 does all of the important stuff 🤯
|
|
610
|
-
|
|
611
|
-
#### Manual configuration
|
|
433
|
+
Backed by the [Qix-/color](https://github.com/Qix-/color) library — `.alpha()`, `.darken()`, `.lighten()`, `.mix()`, etc. See [salty-css.dev/docs/color-function](https://salty-css.dev/docs/color-function).
|
|
612
434
|
|
|
613
|
-
|
|
614
|
-
2. Create `salty.config.ts` to your app directory
|
|
615
|
-
3. Add Salty CSS plugin to next.js config
|
|
435
|
+
## Modifiers
|
|
616
436
|
|
|
617
|
-
|
|
618
|
-
- **Next.js 14 and older:** In `next.config.js` add import for salty plugin `const { withSaltyCss } = require('@salty-css/next');` and then add `withSaltyCss` to wrap your nextConfig export like so `module.exports = withSaltyCss(nextConfig);`
|
|
437
|
+
Custom value transformers registered on `defineConfig`. Each modifier is a `{ pattern, transform }` pair: when Salty sees a string value matching `pattern`, it replaces it with `transform(match).value` (and optionally emits extra CSS via `transform(match).css`).
|
|
619
438
|
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
5. Build `saltygen` directory by running your app once or with cli `npx salty-css build [directory]`
|
|
624
|
-
6. Import global styles from `saltygen/index.css` to some global css file with `@import 'insert_path_to_index_css';`.
|
|
625
|
-
|
|
626
|
-
[Check out Next.js demo project](https://github.com/margarita-form/salty-css-website) or [react example code](#code-examples)
|
|
627
|
-
|
|
628
|
-
---
|
|
629
|
-
|
|
630
|
-
### React + Vite
|
|
631
|
-
|
|
632
|
-

|
|
633
|
-
|
|
634
|
-
### Add Salty CSS to your React + Vite app
|
|
635
|
-
|
|
636
|
-
1. In your existing Vite repository you can run `npx salty-css init` to automatically configure Salty CSS.
|
|
637
|
-
2. Create your first Salty CSS component with `npx salty-css generate [filePath]` (e.g. src/custom-wrapper)
|
|
638
|
-
3. Import your component for example to `main.tsx` and see it working!
|
|
639
|
-
|
|
640
|
-
And note: steps 2 & 3 are just to show how get new components up and running, step 1 does all of the important stuff 🤯
|
|
641
|
-
|
|
642
|
-
### Test it out
|
|
439
|
+
```ts
|
|
440
|
+
// salty.config.ts
|
|
441
|
+
import { defineConfig } from '@salty-css/core/config';
|
|
643
442
|
|
|
644
|
-
|
|
443
|
+
export const config = defineConfig({
|
|
444
|
+
modifiers: {
|
|
445
|
+
spaceShorthand: {
|
|
446
|
+
pattern: /^space:(\d+)$/,
|
|
447
|
+
transform: (match) => {
|
|
448
|
+
const n = Number(match.replace('space:', ''));
|
|
449
|
+
return { value: `${n * 4}px` };
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
```
|
|
645
455
|
|
|
646
|
-
|
|
456
|
+
```ts
|
|
457
|
+
styled('div', { base: { padding: 'space:3' } }); // → padding: 12px
|
|
458
|
+
```
|
|
647
459
|
|
|
648
|
-
|
|
460
|
+
See [salty-css.dev/docs/modifiers](https://salty-css.dev/docs/modifiers).
|
|
649
461
|
|
|
650
|
-
|
|
651
|
-
2. In `vite.config` add import for salty plugin `import { saltyPlugin } from '@salty-css/vite';` and then add `saltyPlugin(__dirname)` to your vite configuration plugins
|
|
652
|
-
3. Make sure that `salty.config.ts` and `vite.config.ts` are in the same folder!
|
|
653
|
-
4. Build `saltygen` directory by running your app once or with cli `npx salty-css build [directory]`
|
|
654
|
-
5. Import global styles from `saltygen/index.css` to some global css file with `@import 'insert_path_to_index_css';`.
|
|
462
|
+
## `withSaltyCss` (Next.js)
|
|
655
463
|
|
|
656
|
-
|
|
464
|
+
```ts
|
|
465
|
+
// next.config.ts
|
|
466
|
+
import { withSaltyCss } from '@salty-css/next';
|
|
657
467
|
|
|
658
|
-
|
|
468
|
+
const nextConfig = {
|
|
469
|
+
/* your config */
|
|
470
|
+
};
|
|
471
|
+
export default withSaltyCss(nextConfig);
|
|
472
|
+
// Or pin a bundler: withSaltyCss(nextConfig, { bundler: 'webpack' })
|
|
473
|
+
```
|
|
659
474
|
|
|
660
|
-
|
|
475
|
+
Options: `mode` (build mode override), `bundler` (`'auto' | 'webpack' | 'turbopack'`, defaults to auto-detect via `process.env.TURBOPACK`), `dir` (project root for Turbopack — defaults to `nextConfig.turbopack.root` or `process.cwd()`).
|
|
661
476
|
|
|
662
|
-
|
|
477
|
+
Next.js 14 CommonJS:
|
|
663
478
|
|
|
664
|
-
|
|
479
|
+
```js
|
|
480
|
+
const { withSaltyCss } = require('@salty-css/next');
|
|
481
|
+
module.exports = withSaltyCss(nextConfig);
|
|
482
|
+
```
|
|
665
483
|
|
|
666
|
-
|
|
484
|
+
See [salty-css.dev/docs/installation](https://salty-css.dev/docs/installation).
|
|
667
485
|
|
|
668
|
-
|
|
486
|
+
## `saltyPlugin` (Vite)
|
|
669
487
|
|
|
670
|
-
```
|
|
671
|
-
|
|
488
|
+
```ts
|
|
489
|
+
// vite.config.ts
|
|
490
|
+
import { defineConfig } from 'vite';
|
|
491
|
+
import { saltyPlugin } from '@salty-css/vite';
|
|
672
492
|
|
|
673
|
-
export
|
|
674
|
-
|
|
675
|
-
colors: {
|
|
676
|
-
brand: '#111',
|
|
677
|
-
highlight: 'yellow',
|
|
678
|
-
},
|
|
679
|
-
},
|
|
680
|
-
global: {
|
|
681
|
-
html: {
|
|
682
|
-
backgroundColor: '#f8f8f8',
|
|
683
|
-
},
|
|
684
|
-
},
|
|
493
|
+
export default defineConfig({
|
|
494
|
+
plugins: [saltyPlugin(__dirname)],
|
|
685
495
|
});
|
|
686
496
|
```
|
|
687
497
|
|
|
688
|
-
|
|
498
|
+
Options: `{ mode }`.
|
|
689
499
|
|
|
690
|
-
|
|
691
|
-
import { styled } from '@salty-css/react/styled';
|
|
500
|
+
## `saltyPlugin` (Webpack)
|
|
692
501
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
502
|
+
```js
|
|
503
|
+
// webpack.config.js
|
|
504
|
+
const { saltyPlugin } = require('@salty-css/webpack');
|
|
505
|
+
|
|
506
|
+
module.exports = (env, argv) => {
|
|
507
|
+
const config = {
|
|
508
|
+
/* … */
|
|
509
|
+
};
|
|
510
|
+
saltyPlugin(config, __dirname);
|
|
511
|
+
return config;
|
|
512
|
+
};
|
|
699
513
|
```
|
|
700
514
|
|
|
701
|
-
|
|
515
|
+
Signature: `saltyPlugin(config, dir, isServer?, cjs?, { mode? })`.
|
|
702
516
|
|
|
703
|
-
|
|
704
|
-
import { styled } from '@salty-css/react/styled';
|
|
517
|
+
## `saltyIntegration` (Astro)
|
|
705
518
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
cursor: 'pointer',
|
|
714
|
-
transition: '200ms',
|
|
715
|
-
textDecoration: 'none',
|
|
716
|
-
'&:hover': {
|
|
717
|
-
background: 'black',
|
|
718
|
-
borderColor: 'black',
|
|
719
|
-
color: 'white',
|
|
720
|
-
},
|
|
721
|
-
'&:disabled': {
|
|
722
|
-
opacity: 0.25,
|
|
723
|
-
pointerEvents: 'none',
|
|
724
|
-
},
|
|
725
|
-
},
|
|
726
|
-
variants: {
|
|
727
|
-
variant: {
|
|
728
|
-
outlined: {
|
|
729
|
-
// same as default styles
|
|
730
|
-
},
|
|
731
|
-
solid: {
|
|
732
|
-
'&:not(:hover)': {
|
|
733
|
-
background: 'black',
|
|
734
|
-
borderColor: 'black',
|
|
735
|
-
color: 'white',
|
|
736
|
-
},
|
|
737
|
-
'&:hover': {
|
|
738
|
-
background: 'transparent',
|
|
739
|
-
borderColor: 'currentColor',
|
|
740
|
-
color: 'currentColor',
|
|
741
|
-
},
|
|
742
|
-
},
|
|
743
|
-
},
|
|
744
|
-
},
|
|
519
|
+
```ts
|
|
520
|
+
// astro.config.mjs
|
|
521
|
+
import { defineConfig } from 'astro/config';
|
|
522
|
+
import saltyIntegration from '@salty-css/astro/integration';
|
|
523
|
+
|
|
524
|
+
export default defineConfig({
|
|
525
|
+
integrations: [saltyIntegration()],
|
|
745
526
|
});
|
|
746
527
|
```
|
|
747
528
|
|
|
748
|
-
|
|
529
|
+
Options: `srcDir` (defaults to `'src'`), `rootDir` (defaults to the Astro config root).
|
|
749
530
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
export const IndexPage = () => {
|
|
755
|
-
return (
|
|
756
|
-
<Wrapper>
|
|
757
|
-
<Button variant="solid" onClick={() => alert('It is a button.')}>
|
|
758
|
-
Outlined
|
|
759
|
-
</Button>
|
|
760
|
-
</Wrapper>
|
|
761
|
-
);
|
|
762
|
-
};
|
|
763
|
-
```
|
|
531
|
+
---
|
|
532
|
+
|
|
533
|
+
## Support
|
|
764
534
|
|
|
765
|
-
|
|
535
|
+
Help, questions, or feedback → [Join the Salty CSS Discord](https://discord.gg/R6kr4KxMhP). Bug reports → [GitHub issues](https://github.com/margarita-form/salty-css/issues).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salty-css/vite",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.37",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"typings": "./dist/index.d.ts",
|
|
@@ -34,11 +34,11 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@salty-css/core": "0.1.0-alpha.
|
|
37
|
+
"@salty-css/core": "0.1.0-alpha.37",
|
|
38
38
|
"vite": "^6.4.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@salty-css/react": "0.1.0-alpha.
|
|
41
|
+
"@salty-css/react": "0.1.0-alpha.37"
|
|
42
42
|
},
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@salty-css/react": {
|