|
1 |
| -# 💧 MistCSS |
| 1 | +# MistCSS |
2 | 2 |
|
3 |
| -[](https://github.com/typicode/mistcss/actions/workflows/node.js.yml) |
| 3 | +> Simplicity is the ultimate sophistication |
4 | 4 |
|
5 |
| -> Create components with 50% less code |
| 5 | +MistCSS lets you create reusable visual components without JavaScript or TypeScript (_think about it for a second... no JS/TS needed_). |
6 | 6 |
|
7 |
| -MistCSS is a new, better and faster way to write visual components. ~~CSS-in-JS~~? Nope! JS-from-CSS 👍 |
| 7 | +Leverage native HTML and CSS, get type safety and auto completion. Just clean and efficient styling. |
8 | 8 |
|
9 |
| -All major frameworks are supported. |
| 9 | +<img width="1116" alt="Screenshot 2024-11-01 at 03 47 44" src="https://github.com/user-attachments/assets/74aea071-be00-4d03-b43a-e46d6282e4b5"> |
10 | 10 |
|
11 |
| -## 1. Write your component in CSS only |
| 11 | +_What you see above is standard HTML ([data-attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*)) and CSS ([nested CSS](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting)). MistCSS simply creates a `d.ts` file based on your CSS._ |
12 | 12 |
|
13 |
| -`./src/Button.mist.css` |
| 13 | +## Features |
| 14 | + |
| 15 | +- 🥶 Below zero-runtime, it's zero JavaScript. Smaller bundles and faster code. |
| 16 | +- 💎 What you write is what you get. No transformations, easy debugging. |
| 17 | +- 🎒 Standards-based, reusable styles across frameworks, compatible with Tailwind or any CSS framework |
| 18 | +- ⚡️ Instantly productive, no learning curve, simple onboarding. |
| 19 | +- 💖 Back to basics with a modern twist: access the full power of HTML and CSS, enhanced with type safety and code completion (without the complexity). |
| 20 | + |
| 21 | +## Usage |
| 22 | + |
| 23 | +Traditional approaches require wrapping your markup/styles in JavaScript functions (`Button.tsx` → `<button/>`, `Input.tsx` → `<input/>`, ...), defining props with TypeScript types, and writing logic to manage class names. |
| 24 | + |
| 25 | +With MistCSS, styling is straightforward and minimal. Here’s how it looks: |
| 26 | + |
| 27 | +`mist.css` |
14 | 28 |
|
15 | 29 | ```css
|
16 |
| -@scope (button.custom-button) { |
17 |
| - :scope { |
18 |
| - background: black; |
| 30 | +button { |
| 31 | + border-radius: 1rem; |
| 32 | + padding: 1rem; |
| 33 | + background: lightgray; |
| 34 | + |
| 35 | + &[data-variant='primary'] { |
| 36 | + background-color: black; |
19 | 37 | color: white;
|
| 38 | + } |
| 39 | + |
| 40 | + &[data-variant='secondary'] { |
| 41 | + background-color: grey; |
| 42 | + color: white; |
| 43 | + } |
| 44 | +} |
| 45 | +``` |
20 | 46 |
|
21 |
| - /* 👇 Define component's props directly in your CSS */ |
22 |
| - &[data-variant='primary'] { |
23 |
| - background: blue; |
24 |
| - } |
| 47 | +`Page.tsx` |
25 | 48 |
|
26 |
| - &[data-variant='secondary'] { |
27 |
| - background: gray; |
28 |
| - } |
| 49 | +```jsx |
| 50 | +<> |
| 51 | + <button data-variant="primary">Save</button> |
| 52 | + <button data-variant="tertiary">Save</button> {/* TS error, tertiary isn't valid */} |
| 53 | +</> |
| 54 | +``` |
| 55 | + |
| 56 | +Output |
| 57 | + |
| 58 | +```jsx |
| 59 | +<button data-variant="primary">Save</button> {/* Same as in Page.tsx */} |
| 60 | +``` |
| 61 | + |
| 62 | +_This example demonstrates enums, but MistCSS also supports boolean and string props. For more details, see the FAQ._ |
| 63 | + |
| 64 | +## How does it work? |
| 65 | + |
| 66 | +MistCSS parses your `mist.css` file and generates `mist.d.ts` for type safety. |
| 67 | + |
| 68 | +For instance, here’s the generated `mist.d.ts` for our button component: |
| 69 | + |
| 70 | +```typescript |
| 71 | +interface Mist_button extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLButtonElement>, HTMLButtonElement> { |
| 72 | + 'data-variant'?: 'primary' | 'secondary' |
| 73 | +} |
| 74 | + |
| 75 | +declare namespace JSX { |
| 76 | + interface IntrinsicElements { |
| 77 | + button: Mist_button // ← <button/> is extended at JSX level to allow 'primary' and 'secondary' values |
29 | 78 | }
|
30 | 79 | }
|
31 | 80 | ```
|
32 | 81 |
|
33 |
| -## 2. Run MistCSS codegen |
| 82 | +That’s it! Simple yet powerful, built entirely on browser standards and TypeScript/JSX. |
| 83 | + |
| 84 | +## Install |
| 85 | + |
| 86 | +```sh |
| 87 | +npm install mistcss --save-dev |
| 88 | +``` |
| 89 | + |
| 90 | +`postcss.config.js` |
| 91 | + |
| 92 | +```js |
| 93 | +module.exports = { |
| 94 | + plugins: { |
| 95 | + mistcss: {}, |
| 96 | + }, |
| 97 | +} |
| 98 | +``` |
| 99 | + |
| 100 | +`layout.tsx` |
34 | 101 |
|
35 |
| -```shell |
36 |
| -mistcss ./src --target=react --watch |
37 |
| -# It will create ./src/Button.mist.tsx |
| 102 | +```ts |
| 103 | +import './mist.css' |
38 | 104 | ```
|
39 | 105 |
|
40 |
| -## 3. Get a type-safe component without writing TypeScript |
| 106 | +## FAQ |
| 107 | + |
| 108 | +### Can I use CSS frameworks like Tailwind or Open Props? |
| 109 | + |
| 110 | +Absolutely, MistCSS is pure HTML and CSS, generating only `mist.d.ts`, so there are no limitations. You can integrate any CSS framework seamlessly. Here are a few examples to get you started: |
| 111 | + |
| 112 | +#### Tailwind v3 ([@apply](https://tailwindcss.com/docs/functions-and-directives#apply)) |
| 113 | + |
| 114 | +```css |
| 115 | +button { |
| 116 | + @apply bg-blue-500 text-white; |
| 117 | + /* ... */ |
| 118 | +} |
| 119 | +``` |
| 120 | + |
| 121 | +#### Tailwind v3 ([theme](https://tailwindcss.com/docs/functions-and-directives#theme)) |
| 122 | + |
| 123 | +```css |
| 124 | +button { |
| 125 | + background: theme(colors.blue.500); |
| 126 | + /* ... */ |
| 127 | +} |
| 128 | +``` |
| 129 | + |
| 130 | +#### Tailwind v4 |
| 131 | + |
| 132 | +Tailwind v4 will support CSS variables natively (see [blog post](https://tailwindcss.com/blog/tailwindcss-v4-alpha |
| 133 | +)). |
41 | 134 |
|
42 |
| -`./src/App.tsx` |
| 135 | +#### Tailwind (inline style) |
| 136 | + |
| 137 | +To override some styles, you can use `className` |
| 138 | + |
| 139 | +```jsx |
| 140 | +<button data-variant="primary" className="p-12"> |
| 141 | + Save |
| 142 | +</button> |
| 143 | +``` |
| 144 | + |
| 145 | +#### Open Props |
| 146 | + |
| 147 | +```css |
| 148 | +button { |
| 149 | + background-color: var(--blue-6); |
| 150 | + /* ... */ |
| 151 | +} |
| 152 | +``` |
| 153 | + |
| 154 | +### Can I do X without JavaScript? |
| 155 | + |
| 156 | +CSS is more powerful than ever, before reaching for JS, explore if native CSS features can accomplish what you need. |
| 157 | + |
| 158 | +### How to write enum, boolean, string props and conditions? |
| 159 | + |
| 160 | +```css |
| 161 | +div[data-component='section'] |
| 162 | + /* CSS variables */ |
| 163 | + --color: ...; |
| 164 | + |
| 165 | + /* Default styles */ |
| 166 | + background: var(--color, green); |
| 167 | + margin: ...; |
| 168 | + padding: ...; |
| 169 | + |
| 170 | + /* Enum props */ |
| 171 | + &[data-size="sm"] { ... } |
| 172 | + &[data-size="lg"] { ... } |
| 173 | + |
| 174 | + /* Boolean props */ |
| 175 | + &[data-is-active] { ... } |
| 176 | + |
| 177 | + /* Condition: size="lg" && is-active */ |
| 178 | + &[data-size="lg"]&[data-is-active] { ... } |
| 179 | + |
| 180 | + /* Condition: size="lg" && !is-active */ |
| 181 | + &[data-size="lg"]:not([data-is-active]) { ... } |
| 182 | +} |
| 183 | +``` |
| 184 | + |
| 185 | +```jsx |
| 186 | +<div |
| 187 | + data-component="section" |
| 188 | + data-size="foo" |
| 189 | + data-is-active |
| 190 | + style={{ '--color': 'red' }} |
| 191 | +/> |
| 192 | +``` |
| 193 | + |
| 194 | +### How to re-use the same tag |
| 195 | + |
| 196 | +If you want both basic links and button-styled links, here’s how you can do: |
| 197 | + |
| 198 | +```css |
| 199 | +a { /* ... */ } |
| 200 | + |
| 201 | +a[data-component='button'] { /* ... */ |
| 202 | + &[data-variant='primary'] { /* ... */ } |
| 203 | +} |
| 204 | +``` |
43 | 205 |
|
44 | 206 | ```jsx
|
45 |
| -import { CustomButton } from './Button.mist' |
| 207 | +<> |
| 208 | + <a href="/home">Home</a> |
| 209 | + <a href="/home" data-component="button">Home</a> |
| 210 | + <a href="/home" data-component="button" data-variant="primary">Home</a> |
| 211 | + <a href="/home" data-variant="primary">Home</a> {/* TS error, `data-variant` is only valid with `data-component="button"` */} |
| 212 | +</> |
| 213 | +``` |
| 214 | + |
| 215 | +> [!NOTE] |
| 216 | +> `data-component` is just a naming convention. Feel free to use any attribute, like `data-style='button'` or `data-button`. It’s simply a way to differentiate between components using the same tag. |
| 217 | +
|
| 218 | + |
| 219 | +### How to build complex components? |
46 | 220 |
|
47 |
| -export const App = () => ( |
48 |
| - <> |
49 |
| - <CustomButton variant="primary">Save</CustomButton> |
| 221 | +`mist.css` |
50 | 222 |
|
51 |
| - {/* TypeScript will catch the error */} |
52 |
| - <CustomButton variant="tertiary">Cancel</CustomButton> |
53 |
| - </> |
54 |
| -) |
| 223 | +```css |
| 224 | +article[data-component='card'] { /* ... */ } |
| 225 | +div[data-component='card-title'] { /* ... */ } |
| 226 | +div[data-component='card-content'] { /* ... */ } |
| 227 | +``` |
| 228 | + |
| 229 | +`Card.jsx` |
| 230 | + |
| 231 | +```jsx |
| 232 | +export function Card({ title, children }) { |
| 233 | + return ( |
| 234 | + <article data-component="card"> |
| 235 | + <div data-component="card-title">{title}</div> |
| 236 | + <div data-component="card-content">{children}</div> |
| 237 | + </article> |
| 238 | + ) |
| 239 | +} |
55 | 240 | ```
|
56 | 241 |
|
57 |
| -MistCSS can generate ⚛️ **React**, 💚 **Vue**, 🚀 **Astro**, 🧠**Svelte** and 🔥 **Hono** components. You can use 🍃 **Tailwind CSS** to style them. |
| 242 | +> [!TIP] |
| 243 | +> To indicate that these styles aren't meant to be used outside of `Card`, you can name them `data-p-component` (`p` for `private`) or use another naming convention. |
58 | 244 |
|
59 |
| -## Documentation |
| 245 | +### How to define CSS variables |
| 246 | + |
| 247 | +```css |
| 248 | +:root { |
| 249 | + --primary-color: #007bff; |
| 250 | + --secondary-color: #6c757d; |
| 251 | +} |
| 252 | + |
| 253 | +button { |
| 254 | + background: var(--primary-color) |
| 255 | + /* ... */ |
| 256 | +``` |
60 | 257 |
|
61 |
| -https://typicode.github.io/mistcss |
| 258 | +See also your CSS framework/tooling documentation for ways to define them in JS if you prefer. |
62 | 259 |
|
63 |
| -## Supports |
| 260 | +### Origin of the project name? |
64 | 261 |
|
65 |
| -- [Next.js](https://nextjs.org/) |
66 |
| -- [Remix](https://remix.run/) |
67 |
| -- [React](https://react.dev/) |
68 |
| -- [Vue](https://vuejs.org) |
69 |
| -- [Svelte](https://svelte.dev/) |
70 |
| -- [Astro](https://astro.build/) |
71 |
| -- [Hono](https://hono.dev/) |
72 |
| -- [Tailwind CSS](https://tailwindcss.com/) |
| 262 | +Mist is inspired by atomized water 💧 often seen near waterfalls. A nod to the _Cascading_ in CSS 🌊. |
0 commit comments