` and `` ([#313](https://github.com/tailwindlabs/tailwindcss-typography/pull/313))
59 | - Remove typography styles from `not-prose` elements in addition to their children ([#301](https://github.com/tailwindlabs/tailwindcss-typography/pull/301))
60 | - Add `` styles ([#314](https://github.com/tailwindlabs/tailwindcss-typography/pull/314))
61 | - Fix `prose-invert` when used with colors in light mode ([#315](https://github.com/tailwindlabs/tailwindcss-typography/pull/315))
62 | - Add `` styles ([#317](https://github.com/tailwindlabs/tailwindcss-typography/pull/317))
63 | - Add description list (``, `- `, `
- `) styles ([#316](https://github.com/tailwindlabs/tailwindcss-typography/pull/316))
64 |
65 | ## [0.5.9] - 2023-01-10
66 |
67 | ### Fixed
68 |
69 | - Ensure `p` styles are inserted before `.lead` styles ([#294](https://github.com/tailwindlabs/tailwindcss-typography/pull/294))
70 |
71 | ## [0.5.8] - 2022-11-07
72 |
73 | ### Fixed
74 |
75 | - Fix selector when using a non-default class (e.g. `prose-sm`) ([#289](https://github.com/tailwindlabs/tailwindcss-typography/pull/289))
76 |
77 | ## [0.5.7] - 2022-09-02
78 |
79 | ### Fixed
80 |
81 | - Update TypeScript types ([#284](https://github.com/tailwindlabs/tailwindcss-typography/pull/284))
82 |
83 | ## [0.5.6] - 2022-09-01
84 |
85 | - Actually publish types ([a54c1a8](https://github.com/tailwindlabs/tailwindcss-typography/commit/a54c1a82a64efdf23aab57e62edaa369d1a857f1))
86 |
87 | ## [0.5.5] - 2022-09-01
88 |
89 | ### Added
90 |
91 | - Add typescript types ([#283](https://github.com/tailwindlabs/tailwindcss-typography/pull/283))
92 |
93 | ## [0.5.4] - 2022-07-12
94 |
95 | ### Fixed
96 |
97 | - Update `strong` and `code` color styles to inherit from parent ([#276](https://github.com/tailwindlabs/tailwindcss-typography/pull/276))
98 |
99 | ## [0.5.3] - 2022-07-07
100 |
101 | ### Added
102 |
103 | - Add styles for `tfoot` elements ([#243](https://github.com/tailwindlabs/tailwindcss-typography/pull/243))
104 | - Add `prose-h5` and `prose-h6` variants ([#273](https://github.com/tailwindlabs/tailwindcss-typography/pull/273))
105 |
106 | ### Fixed
107 |
108 | - Fix prose elements `legacy` mode ([#259](https://github.com/tailwindlabs/tailwindcss-typography/pull/259))
109 | - Allow `lead` class to override element styles ([#260](https://github.com/tailwindlabs/tailwindcss-typography/pull/260))
110 | - Fix generation of `prose-headings` variant ([#264](https://github.com/tailwindlabs/tailwindcss-typography/pull/264))
111 | - Fix `figure` spacing ([#267](https://github.com/tailwindlabs/tailwindcss-typography/pull/267))
112 | - Fix child combinator `:where` selectors ([#268](https://github.com/tailwindlabs/tailwindcss-typography/pull/267))
113 | - Fix `prose-headings` variant to include `h5` and `h6` elements ([#273](https://github.com/tailwindlabs/tailwindcss-typography/pull/273))
114 |
115 | ## [0.5.2] - 2022-02-14
116 |
117 | ### Fixed
118 |
119 | - Ensure nested selectors using `&:hover` work ([#246](https://github.com/tailwindlabs/tailwindcss-typography/pull/246))
120 |
121 | ## [0.5.1] - 2022-01-28
122 |
123 | ### Removed
124 |
125 | - Remove `dist` folder and related dependencies ([#226](https://github.com/tailwindlabs/tailwindcss-typography/pull/226))
126 |
127 | ### Fixed
128 |
129 | - Don't generate invalid CSS when given an array of property values ([#224](https://github.com/tailwindlabs/tailwindcss-typography/pull/224))
130 |
131 | ## [0.5.0] - 2021-12-09
132 |
133 | ## [0.5.0-alpha.3] - 2021-11-08
134 |
135 | ### Changes
136 |
137 | - Use `:where` to reduce specificity, making it possible to override prose children with utilities ([#203](https://github.com/tailwindlabs/tailwindcss-typography/pull/203))
138 | - Support "undoing" prose styles using the `not-prose` class on a group of child elements ([#205](https://github.com/tailwindlabs/tailwindcss-typography/pull/205))
139 | - Update color palette for v3 ([#206](https://github.com/tailwindlabs/tailwindcss-typography/pull/206))
140 | - Improve customization API, add alternate grays, add dark mode support, use `::marker` instead of pseudo-elements, and add child element variants (eg. `prose-h1:underline`)([#216](https://github.com/tailwindlabs/tailwindcss-typography/pull/216))
141 |
142 | ## [0.5.0-alpha.2] - 2021-11-08
143 |
144 | ## [0.5.0-alpha.1] - 2021-11-08
145 |
146 | ## [0.4.1] - 2021-05-24
147 |
148 | ### Fixed
149 |
150 | - Fix list-style modifier selectors ([#137](https://github.com/tailwindlabs/tailwindcss-typography/pull/137))
151 |
152 | ## [0.4.0] - 2021-01-15
153 |
154 | ### Fixed
155 |
156 | - Colors without `600` variant, breaks everything (#107)
157 | - Fix empty line in firefox for `pre code` tags (#125)
158 |
159 | ### Added
160 |
161 | - Add support for the `start` and `reversed` attribute on `ol` elements (#110)
162 | - Add support for the `type` on `ul` and `ol` elements (#126)
163 |
164 | ### Changed (internal)
165 |
166 | - Bumped dependencies (#103, #115)
167 | - Cleanup/improve readme (#95)
168 | - Reduce package size (#112)
169 |
170 | ## [0.3.0] - 2020-11-20
171 |
172 | ## Changed
173 |
174 | - Add support for Tailwind CSS v2.0 and drops support for v1.0 (#79, #82, #87)
175 | - Use `extend` for any overrides, assigning directly to `theme.typography` now completely overrides default configuration
176 | - Consistently use `::before` instead of `:before` ([ba33d77](https://github.com/tailwindlabs/tailwindcss-typography/commit/ba33d77f25ab0c239edd7d425349c86f317061e2))
177 | - Read color values from the user's theme instead of only the default theme
178 |
179 | ## Added
180 |
181 | - Add new `className` option for overriding `prose` class (#28)
182 | - Add color modifiers by default like `prose-blue` for setting link styles (#92)
183 |
184 | ## [0.2.0] - 2020-07-15
185 |
186 | ### Changes
187 |
188 | - Switches how variants are generated to use the new component variants API in Tailwind 1.5, dropping support for Tailwind < 1.5
189 |
190 | ## [0.1.4] - 2020-07-15
191 |
192 | ### Fixes
193 |
194 | - Fixes an issue where the `lead` class was not being applied correctly when used in conjunction with responsive variants
195 |
196 | ## [0.1.3] - 2020-07-14
197 |
198 | ### Fixes
199 |
200 | - Fixes an issue where the configured monospace font family was not used by default for `pre` and `code` blocks
201 |
202 | ## [0.1.2] - 2020-07-14
203 |
204 | ### Fixes
205 |
206 | - Fixes an issue in our internal rounding logic that would round values like `20` to `2` 👀
207 |
208 | ## [0.1.1] - 2020-07-14
209 |
210 | ### Fixes
211 |
212 | - Fixes an issue where the plugin was totally broken and didn't work at all because I deleted some code without even testing my changes 🧠
213 |
214 | ## [0.1.0] - 2020-07-14
215 |
216 | ### Added
217 |
218 | - Everything!
219 |
220 | [unreleased]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.16...HEAD
221 | [0.5.16]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.15...v0.5.16
222 | [0.5.15]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.14...v0.5.15
223 | [0.5.14]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.13...v0.5.14
224 | [0.5.13]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.12...v0.5.13
225 | [0.5.12]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.11...v0.5.12
226 | [0.5.11]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.10...v0.5.11
227 | [0.5.10]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.9...v0.5.10
228 | [0.5.9]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.8...v0.5.9
229 | [0.5.8]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.7...v0.5.8
230 | [0.5.7]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.6...v0.5.7
231 | [0.5.6]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.5...v0.5.6
232 | [0.5.5]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.4...v0.5.5
233 | [0.5.4]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.3...v0.5.4
234 | [0.5.3]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.2...v0.5.3
235 | [0.5.2]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.1...v0.5.2
236 | [0.5.1]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.0...v0.5.1
237 | [0.5.0]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.0-alpha.3...v0.5.0
238 | [0.5.0-alpha.3]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.0-alpha.2...v0.5.0-alpha.3
239 | [0.5.0-alpha.2]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.5.0-alpha.1...v0.5.0-alpha.2
240 | [0.5.0-alpha.1]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.4.1...v0.5.0-alpha.1
241 | [0.4.1]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.4.0...v0.4.1
242 | [0.4.0]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.3.0...v0.4.0
243 | [0.3.0]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.2.0...v0.3.0
244 | [0.2.0]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.1.4...v0.2.0
245 | [0.1.4]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.1.3...v0.1.4
246 | [0.1.3]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.1.2...v0.1.3
247 | [0.1.2]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.1.1...v0.1.2
248 | [0.1.1]: https://github.com/tailwindlabs/tailwindcss-typography/compare/v0.1.0...v0.1.1
249 | [0.1.0]: https://github.com/tailwindlabs/tailwindcss-typography/releases/tag/v0.1.0
250 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Tailwind Labs, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | The official Tailwind CSS Typography plugin provides a set of `prose` classes you can use to add beautiful typographic defaults to any vanilla HTML you don’t control, like HTML rendered from Markdown, or pulled from a CMS.
12 |
13 | ```html
14 | {{ markdown }}
15 | ```
16 |
17 | To see what it looks like in action, check out our [live demo](https://play.tailwindcss.com/uj1vGACRJA?layout=preview) on Tailwind Play.
18 |
19 | ---
20 |
21 | ## Installation
22 |
23 | Install the plugin from npm:
24 |
25 | ```shell
26 | npm install -D @tailwindcss/typography
27 | ```
28 |
29 | Then add the plugin to your main `style.css` file:
30 |
31 | ```diff
32 | @import "tailwindcss";
33 | + @plugin "@tailwindcss/typography";
34 | ```
35 |
36 | ---
37 |
38 | ## Basic usage
39 |
40 | Now you can use the `prose` classes to add sensible typography styles to any vanilla HTML:
41 |
42 | ```html
43 |
44 | Garlic bread with cheese: What the science tells us
45 |
46 | For years parents have espoused the health benefits of eating garlic bread with cheese to their
47 | children, with the food earning such an iconic status in our culture that kids will often dress
48 | up as warm, cheesy loaf for Halloween.
49 |
50 |
51 | But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
52 | springing up around the country.
53 |
54 |
55 |
56 | ```
57 |
58 | ### Choosing a gray scale
59 |
60 | This plugin includes a modifier class for each of the five gray scales Tailwind includes by default so you can easily style your content to match the grays you're using in your project.
61 |
62 | ```html
63 | {{ markdown }}
64 | ```
65 |
66 | Here are the classes that are generated using a totally default Tailwind CSS v2.0 build:
67 |
68 | | Class | Gray scale |
69 | | ------------------------ | ---------- |
70 | | `prose-gray` _(default)_ | Gray |
71 | | `prose-slate` | Slate |
72 | | `prose-zinc` | Zinc |
73 | | `prose-neutral` | Neutral |
74 | | `prose-stone` | Stone |
75 |
76 | Modifier classes are designed to be used with the [multi-class modifier pattern](http://nicolasgallagher.com/about-html-semantics-front-end-architecture/#component-modifiers) and must be used in conjunction with the base `prose` class.
77 |
78 | > [!NOTE]
79 | > Always include the `prose` class when adding a gray scale modifier
80 |
81 | ```html
82 | {{ markdown }}
83 | ```
84 |
85 | To learn about creating your own color themes, read the [adding custom color themes](#adding-custom-color-themes) documentation.
86 |
87 | ### Applying a type scale
88 |
89 | Size modifiers allow you to adjust the overall size of your typography for different contexts.
90 |
91 | ```html
92 | {{ markdown }}
93 | ```
94 |
95 | Five different typography sizes are included out of the box:
96 |
97 | | Class | Body font size |
98 | | ------------------------ | ----------------- |
99 | | `prose-sm` | 0.875rem _(14px)_ |
100 | | `prose-base` _(default)_ | 1rem _(16px)_ |
101 | | `prose-lg` | 1.125rem _(18px)_ |
102 | | `prose-xl` | 1.25rem _(20px)_ |
103 | | `prose-2xl` | 1.5rem _(24px)_ |
104 |
105 | These can be used in combination with Tailwind's [breakpoint modifiers](https://tailwindcss.com/docs/responsive-design) to change the overall font size of a piece of content at different viewport sizes:
106 |
107 | ```html
108 | {{ markdown }}
109 | ```
110 |
111 | Everything about the provided size modifiers has been hand-tuned by professional designers to look as beautiful as possible, including the relationships between font sizes, heading spacing, code block padding, and more.
112 |
113 | Size modifiers are designed to be used with the [multi-class modifier pattern](http://nicolasgallagher.com/about-html-semantics-front-end-architecture/#component-modifiers) and must be used in conjunction with the base `prose` class.
114 |
115 | > [!NOTE]
116 | > Always include the `prose` class when adding a size modifier
117 |
118 | ```html
119 | {{ markdown }}
120 | ```
121 |
122 | To learn about customizing the included type scales, read the documentation on [customizing the CSS](#customizing-the-css).
123 |
124 | ### Adapting to dark mode
125 |
126 | Each default color theme includes a hand-designed dark mode version that you can trigger by adding the `prose-invert` class:
127 |
128 | ```html
129 | {{ markdown }}
130 | ```
131 |
132 | To learn about creating your own color themes, read the [adding custom color themes](#adding-custom-color-themes) documentation.
133 |
134 | ### Element modifiers
135 |
136 | Use element modifiers to customize the style of individual elements in your content directly in your HTML:
137 |
138 | ```html
139 |
140 | {{ markdown }}
141 |
142 | ```
143 |
144 | This makes it easy to do things like style links to match your brand, add a border radius to images, and tons more.
145 |
146 | Here's a complete list of available element modifiers:
147 |
148 | | Modifier | Target |
149 | | ---------------------------- | ---------------------------- |
150 | | `prose-headings:{utility}` | `h1`, `h2`, `h3`, `h4`, `th` |
151 | | `prose-lead:{utility}` | `[class~="lead"]` |
152 | | `prose-h1:{utility}` | `h1` |
153 | | `prose-h2:{utility}` | `h2` |
154 | | `prose-h3:{utility}` | `h3` |
155 | | `prose-h4:{utility}` | `h4` |
156 | | `prose-p:{utility}` | `p` |
157 | | `prose-a:{utility}` | `a` |
158 | | `prose-blockquote:{utility}` | `blockquote` |
159 | | `prose-figure:{utility}` | `figure` |
160 | | `prose-figcaption:{utility}` | `figcaption` |
161 | | `prose-strong:{utility}` | `strong` |
162 | | `prose-em:{utility}` | `em` |
163 | | `prose-kbd:{utility}` | `kbd` |
164 | | `prose-code:{utility}` | `code` |
165 | | `prose-pre:{utility}` | `pre` |
166 | | `prose-ol:{utility}` | `ol` |
167 | | `prose-ul:{utility}` | `ul` |
168 | | `prose-li:{utility}` | `li` |
169 | | `prose-table:{utility}` | `table` |
170 | | `prose-thead:{utility}` | `thead` |
171 | | `prose-tr:{utility}` | `tr` |
172 | | `prose-th:{utility}` | `th` |
173 | | `prose-td:{utility}` | `td` |
174 | | `prose-img:{utility}` | `img` |
175 | | `prose-video:{utility}` | `video` |
176 | | `prose-hr:{utility}` | `hr` |
177 |
178 | When stacking these modifiers with other modifiers like `hover`, you most likely want the other modifier to come last:
179 |
180 | ```html
181 | {{ markdown }}
182 | ```
183 |
184 | If you are still using in Tailwind CSS v3, the modifier order is the opposite:
185 |
186 | ```html
187 | {{ markdown }}
188 | ```
189 |
190 | Read the Tailwind CSS documentation on [stacked modifiers](https://tailwindcss.com/docs/hover-focus-and-other-states) to learn more.
191 |
192 | ### Overriding max-width
193 |
194 | Each size modifier comes with a baked in `max-width` designed to keep the content as readable as possible. This isn't always what you want though, and sometimes you'll want the content to just fill the width of its container.
195 |
196 | In those cases, all you need to do is add `max-w-none` to your content to override the embedded max-width:
197 |
198 | ```html
199 |
200 |
201 |
202 |
203 |
204 |
{{ markdown }}
205 |
206 |
207 | ```
208 |
209 | ---
210 |
211 | ## Advanced topics
212 |
213 | ### Undoing typography styles
214 |
215 | If you have a block of markup embedded in some content that shouldn't inherit the `prose` styles, use the `not-prose` class to sandbox it:
216 |
217 | ```html
218 |
219 | My Heading
220 | ...
221 |
222 |
223 |
224 |
225 |
226 | ...
227 |
228 |
229 | ```
230 |
231 | Note that you can't nest new `prose` instances within a `not-prose` block at this time.
232 |
233 | ### Adding custom color themes
234 |
235 | To customize the color theme beyond simple CSS overrides, you can use the JavaScript based theme API. To do that, use the `@config` directive:
236 |
237 | ```diff
238 | @import "tailwindcss";
239 | @plugin "@tailwindcss/typography";
240 | + @config "./tailwind.config.js";
241 | ```
242 |
243 | You can then create your own color theme by adding a new `tailwind.config.js` file with the `typography` section and providing your colors under the `css` key:
244 |
245 | ```js {{ filename: 'tailwind.config.js' }}
246 | /** @type {import('tailwindcss').Config} */
247 | module.exports = {
248 | theme: {
249 | extend: {
250 | typography: () => ({
251 | pink: {
252 | css: {
253 | '--tw-prose-body': 'var(--color-pink-800)',
254 | '--tw-prose-headings': 'var(--color-pink-900)',
255 | '--tw-prose-lead': 'var(--color-pink-700)',
256 | '--tw-prose-links': 'var(--color-pink-900)',
257 | '--tw-prose-bold': 'var(--color-pink-900)',
258 | '--tw-prose-counters': 'var(--color-pink-600)',
259 | '--tw-prose-bullets': 'var(--color-pink-400)',
260 | '--tw-prose-hr': 'var(--color-pink-300)',
261 | '--tw-prose-quotes': 'var(--color-pink-900)',
262 | '--tw-prose-quote-borders': 'var(--color-pink-300)',
263 | '--tw-prose-captions': 'var(--color-pink-700)',
264 | '--tw-prose-code': 'var(--color-pink-900)',
265 | '--tw-prose-pre-code': 'var(--color-pink-100)',
266 | '--tw-prose-pre-bg': 'var(--color-pink-900)',
267 | '--tw-prose-th-borders': 'var(--color-pink-300)',
268 | '--tw-prose-td-borders': 'var(--color-pink-200)',
269 | '--tw-prose-invert-body': 'var(--color-pink-200)',
270 | '--tw-prose-invert-headings': 'var(--color-white)',
271 | '--tw-prose-invert-lead': 'var(--color-pink-300)',
272 | '--tw-prose-invert-links': 'var(--color-white)',
273 | '--tw-prose-invert-bold': 'var(--color-white)',
274 | '--tw-prose-invert-counters': 'var(--color-pink-400)',
275 | '--tw-prose-invert-bullets': 'var(--color-pink-600)',
276 | '--tw-prose-invert-hr': 'var(--color-pink-700)',
277 | '--tw-prose-invert-quotes': 'var(--color-pink-100)',
278 | '--tw-prose-invert-quote-borders': 'var(--color-pink-700)',
279 | '--tw-prose-invert-captions': 'var(--color-pink-400)',
280 | '--tw-prose-invert-code': 'var(--color-white)',
281 | '--tw-prose-invert-pre-code': 'var(--color-pink-300)',
282 | '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
283 | '--tw-prose-invert-th-borders': 'var(--color-pink-600)',
284 | '--tw-prose-invert-td-borders': 'var(--color-pink-700)',
285 | },
286 | },
287 | }),
288 | },
289 | },
290 | }
291 | ```
292 |
293 | See our internal [style definitions](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/src/styles.js) for some more examples.
294 |
295 | ### Changing the default class name
296 |
297 | If you need to use a class name other than `prose` for any reason, you can do so using the `className` option when registering the plugin:
298 |
299 | ```css
300 | @import "tailwindcss";
301 | @plugin "@tailwindcss/typography" {
302 | className: wysiwyg;
303 | }
304 | ```
305 |
306 | Now every instance of `prose` in the default class names will be replaced by your custom class name:
307 |
308 | ```html
309 |
310 | My Heading
311 | ...
312 |
313 |
314 |
315 |
316 |
317 | ...
318 |
319 |
320 | ```
321 |
322 | ### Customizing the CSS
323 |
324 | If you want to customize the raw CSS generated by this plugin, first follow the steps from [adding custom color themes](#adding-custom-color-themes) to set up a JavaScript based configuration API and then add your overrides under the `typography` key in the `theme` section of your `tailwind.config.js` file:
325 |
326 | ```js {{ filename: 'tailwind.config.js' }}
327 | /** @type {import('tailwindcss').Config} */
328 | module.exports = {
329 | theme: {
330 | extend: {
331 | typography: {
332 | DEFAULT: {
333 | css: {
334 | color: '#333',
335 | a: {
336 | color: '#3182ce',
337 | '&:hover': {
338 | color: '#2c5282',
339 | },
340 | },
341 | },
342 | },
343 | },
344 | },
345 | },
346 | }
347 | ```
348 |
349 | Like with all theme customizations in Tailwind, you can use CSS variables if you need access to access your theme configuration:
350 |
351 | ```js {{ filename: 'tailwind.config.js' }}
352 | /** @type {import('tailwindcss').Config} */
353 | module.exports = {
354 | theme: {
355 | extend: {
356 | typography: {
357 | DEFAULT: {
358 | css: {
359 | color: 'var(--color-gray-800)',
360 | // ...
361 | },
362 | },
363 | },
364 | },
365 | },
366 | }
367 | ```
368 |
369 | Customizations should be applied to a specific modifier like `DEFAULT` or `xl`, and must be added under the `css` property. Customizations are authored in the same [CSS-in-JS syntax](https://v3.tailwindcss.com/docs/plugins#css-in-js-syntax) used to write Tailwind v3 plugins.
370 |
371 | See [the default styles](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/src/styles.js) for this plugin for more in-depth examples of configuring each modifier.
372 |
373 | ---
374 |
375 | ## Community
376 |
377 | For help, discussion about best practices, or any other conversation that would benefit from being searchable:
378 |
379 | [Discuss the Tailwind CSS Typography plugin on GitHub](https://github.com/tailwindlabs/tailwindcss/discussions)
380 |
381 | For casual chit-chat with others using the framework:
382 |
383 | [Join the Tailwind CSS Discord Server](https://tailwindcss.com/discord)
384 |
--------------------------------------------------------------------------------
/demo/components/MarkdownSample.mdx:
--------------------------------------------------------------------------------
1 |
2 | Until now, trying to style an article, document, or blog post with Tailwind has been a tedious
3 | task that required a keen eye for typography and a lot of complex custom CSS.
4 |
5 |
6 | By default, Tailwind removes all of the default browser styling from paragraphs, headings, lists and more. This ends up being really useful for building application UIs because you spend less time undoing user-agent styles, but when you _really are_ just trying to style some content that came from a rich-text editor in a CMS or a markdown file, it can be surprising and unintuitive.
7 |
8 | We get lots of complaints about it actually, with people regularly asking us things like:
9 |
10 | > Why is Tailwind removing the default styles on my `h1` elements? How do I disable this? What do you mean I lose all the other base styles too?
11 |
12 | We hear you, but we're not convinced that simply disabling our base styles is what you really want. You don't want to have to remove annoying margins every time you use a `p` element in a piece of your dashboard UI. And I doubt you really want your blog posts to use the user-agent styles either — you want them to look _awesome_, not awful.
13 |
14 | The `@tailwindcss/typography` plugin is our attempt to give you what you _actually_ want, without any of the downsides of doing something stupid like disabling our base styles.
15 |
16 | It adds a new `prose` class that you can slap on any block of vanilla HTML content and turn it into a beautiful, well-formatted document:
17 |
18 | ```html
19 |
20 | Garlic bread with cheese: What the science tells us
21 |
22 | For years parents have espoused the health benefits of eating garlic bread with cheese to their
23 | children, with the food earning such an iconic status in our culture that kids will often dress
24 | up as warm, cheesy loaf for Halloween.
25 |
26 |
27 | But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
28 | springing up around the country.
29 |
30 |
31 |
32 | ```
33 |
34 | For more information about how to use the plugin and the features it includes, [read the documentation](https://github.com/tailwindcss/typography/blob/main/README.md).
35 |
36 | ---
37 |
38 | ## What to expect from here on out
39 |
40 | What follows from here is just a bunch of absolute nonsense I've written to dogfood the plugin itself. It includes every sensible typographic element I could think of, like **bold text**, unordered lists, ordered lists, code blocks, block quotes, _and even italics_.
41 |
42 | It's important to cover all of these use cases for a few reasons:
43 |
44 | 1. We want everything to look good out of the box.
45 | 2. Really just the first reason, that's the whole point of the plugin.
46 | 3. Here's a third pretend reason though a list with three items looks more realistic than a list with two items.
47 |
48 | Now we're going to try out another header style.
49 |
50 | ### Typography should be easy
51 |
52 | So that's a header for you — with any luck if we've done our job correctly that will look pretty reasonable.
53 |
54 | Something a wise person once told me about typography is:
55 |
56 | > Typography is pretty important if you don't want your stuff to look like trash. Make it good then it won't be bad.
57 |
58 | It's probably important that images look okay here by default as well:
59 |
60 |
61 |
65 |
66 | Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of
67 | classical Latin literature from 45 BC, making it over 2000 years old.
68 |
69 |
70 |
71 | Now I'm going to show you an example of an unordered list to make sure that looks good, too:
72 |
73 | - So here is the first item in this list.
74 | - In this example we're keeping the items short.
75 | - Later, we'll use longer, more complex list items.
76 |
77 | And that's the end of this section.
78 |
79 | ## What if we stack headings?
80 |
81 | ### We should make sure that looks good, too.
82 |
83 | Sometimes you have headings directly underneath each other. In those cases you often have to undo the top margin on the second heading because it usually looks better for the headings to be closer together than a paragraph followed by a heading should be.
84 |
85 | ### When a heading comes after a paragraph …
86 |
87 | When a heading comes after a paragraph, we need a bit more space, like I already mentioned above. Now let's see what a more complex list would look like.
88 |
89 | - **I often do this thing where list items have headings.**
90 |
91 | For some reason I think this looks cool which is unfortunate because it's pretty annoying to get the styles right.
92 |
93 | I often have two or three paragraphs in these list items, too, so the hard part is getting the spacing between the paragraphs, list item heading, and separate list items to all make sense. Pretty tough honestly, you could make a strong argument that you just shouldn't write this way.
94 |
95 | - **Since this is a list, I need at least two items.**
96 |
97 | I explained what I'm doing already in the previous list item, but a list wouldn't be a list if it only had one item, and we really want this to look realistic. That's why I've added this second list item so I actually have something to look at when writing the styles.
98 |
99 | - **It's not a bad idea to add a third item either.**
100 |
101 | I think it probably would've been fine to just use two items but three is definitely not worse, and since I seem to be having no trouble making up arbitrary things to type, I might as well include it. I'm going to press Enter now.
102 |
103 | After this sort of list I usually have a closing statement or paragraph, because it kinda looks weird jumping right to a heading.
104 |
105 | ## Code should look okay by default.
106 |
107 | I think most people are going to use [highlight.js](https://highlightjs.org/) or [Prism](https://prismjs.com/) or something if they want to style their code blocks but it wouldn't hurt to make them look _okay_ out of the box, even with no syntax highlighting.
108 |
109 | Here's what a default `tailwind.config.js` file looks like at the time of writing:
110 |
111 | ```js
112 | module.exports = {
113 | purge: [],
114 | theme: {
115 | extend: {},
116 | },
117 | variants: {},
118 | plugins: [],
119 | }
120 | ```
121 |
122 | Hopefully that looks good enough to you.
123 |
124 | ### What about nested lists?
125 |
126 | Nested lists basically always look bad which is why editors like Medium don't even let you do it, but I guess since some of you goofballs are going to do it we have to carry the burden of at least making it work.
127 |
128 | 1. **Nested lists are rarely a good idea.**
129 | - You might feel like you are being really "organized" or something but you are just creating a gross shape on the screen that is hard to read.
130 | - Nested navigation in UIs is a bad idea too, keep things as flat as possible.
131 | - Nesting tons of folders in your source code is also not helpful.
132 | 2. **Since we need to have more items, here's another one.**
133 | - I'm not sure if we'll bother styling more than two levels deep.
134 | - Two is already too much, three is guaranteed to be a bad idea.
135 | - If you nest four levels deep you belong in prison.
136 | 3. **Two items isn't really a list, three is good though.**
137 | - Again please don't nest lists if you want people to actually read your content.
138 | - Nobody wants to look at this.
139 | - I'm upset that we even have to bother styling this.
140 |
141 | The most annoying thing about lists in Markdown is that `- ` elements aren't given a child `
` tag unless there are multiple paragraphs in the list item. That means I have to worry about styling that annoying situation too.
142 |
143 | - **For example, here's another nested list.**
144 |
145 | But this time with a second paragraph.
146 |
147 | - These list items won't have `
` tags
148 | - Because they are only one line each
149 |
150 | - **But in this second top-level list item, they will.**
151 |
152 | This is especially annoying because of the spacing on this paragraph.
153 |
154 | - As you can see here, because I've added a second line, this list item now has a `
` tag.
155 |
156 | This is the second line I'm talking about by the way.
157 |
158 | - Finally here's another list item so it's more like a list.
159 |
160 | - A closing list item, but with no nested list, because why not?
161 |
162 | And finally a sentence to close off this section.
163 |
164 | ## We didn't forget about description lists
165 |
166 | Well, that's not exactly true, we first released this plugin back in 2020 and it took three years before we added description lists. But they're here now, so let's just be happy about that…okay? They can be great for things like FAQs.
167 |
168 |
169 | - Why do you never see elephants hiding in trees?
170 | -
171 | Because they're so good at it. Lorem ipsum dolor sit amet consectetur adipisicing elit. Quas
172 | cupiditate laboriosam fugiat.
173 |
174 | - What do you call someone with no body and no nose?
175 | -
176 | Nobody knows. Lorem ipsum dolor sit amet consectetur adipisicing elit. Culpa, voluptas ipsa quia
177 | excepturi, quibusdam natus exercitationem sapiente tempore labore voluptatem.
178 |
179 | - Why can't you hear a pterodactyl go to the bathroom?
180 | -
181 | Because the pee is silent. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ipsam, quas
182 | voluptatibus ex culpa ipsum, aspernatur blanditiis fugiat ullam magnam suscipit deserunt illum
183 | natus facilis atque vero consequatur! Quisquam, debitis error.
184 |
185 |
186 |
187 | ## There are other elements we need to style
188 |
189 | I almost forgot to mention links, like [this link to the Tailwind CSS website](https://tailwindcss.com). We almost made them blue but that's so yesterday, so we went with dark gray, feels edgier.
190 |
191 | We even included table styles, check it out:
192 |
193 | | Wrestler | Origin | Finisher |
194 | | ----------------------- | ------------ | ------------------ |
195 | | Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
196 | | Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
197 | | Randy Savage | Sarasota, FL | Elbow Drop |
198 | | Vader | Boulder, CO | Vader Bomb |
199 | | Razor Ramon | Chuluota, FL | Razor's Edge |
200 |
201 | We also need to make sure inline code looks good, like if I wanted to talk about `` elements or tell you the good news about `@tailwindcss/typography`.
202 |
203 | ### Sometimes I even use `code` in headings
204 |
205 | Even though it's probably a bad idea, and historically I've had a hard time making it look good. This _"wrap the code blocks in backticks"_ trick works pretty well though really.
206 |
207 | Another thing I've done in the past is put a `code` tag inside of a link, like if I wanted to tell you about the [`tailwindcss/docs`](https://github.com/tailwindcss/docs) repository. I don't love that there is an underline below the backticks but it is absolutely not worth the madness it would require to avoid it.
208 |
209 | #### We haven't used an `h4` yet
210 |
211 | But now we have. Please don't use `h5` or `h6` in your content, Medium only supports two heading levels for a reason, you animals. I honestly considered using a `before` pseudo-element to scream at you if you use an `h5` or `h6`.
212 |
213 | We don't style them at all out of the box because `h4` elements are already so small that they are the same size as the body copy. What are we supposed to do with an `h5`, make it _smaller_ than the body copy? No thanks.
214 |
215 | ### We still need to think about stacked headings though.
216 |
217 | #### Let's make sure we don't screw that up with `h4` elements, either.
218 |
219 | Phew, with any luck we have styled the headings above this text and they look pretty good.
220 |
221 | Let's add a closing paragraph here so things end with a decently sized block of text. I can't explain why I want things to end that way but I have to assume it's because I think things will look weird or unbalanced if there is a heading too close to the end of the document.
222 |
223 | What I've written here is probably long enough, but adding this final sentence can't hurt.
224 |
--------------------------------------------------------------------------------
/demo/components/MarkdownSampleShort.mdx:
--------------------------------------------------------------------------------
1 |
2 | Until now, trying to style an article, document, or blog post with Tailwind has been a very
3 | tedious task.
4 |
5 |
6 | The `@tailwindcss/typography` plugin is our attempt to give you what you _actually_ want, without any of the downsides of doing something stupid like disabling our base styles.
7 |
8 | > Why is Tailwind removing the default styles on my `h1` elements? How do I disable this? What do you mean I lose all the other base styles too?
9 |
10 |
11 |
Shouldn't be colored
12 |
13 |
14 | ```html
15 |
16 | But a recent study shows that the celebrated appetizer may be linked to a series of rabies cases
17 | springing up around the country.
18 |
19 | ```
20 |
21 | For more information about how to use the plugin and the features it includes, [read the documentation](https://github.com/tailwindcss/typography/blob/main/README.md).
22 |
23 | ---
24 |
25 | ## This is a heading
26 |
27 | 1. We want everything to look good out of the box.
28 | 2. Really just the first reason, that's the whole point of the plugin.
29 | 3. Here's a third pretend reason though a list with three items looks more realistic than a list with two items.
30 |
31 | Now **I'm going to show you** an example of an unordered list to make sure that looks good, too:
32 |
33 | - So here is the first item in this list.
34 | - In this example we're keeping the items short.
35 | - Later, we'll use longer, more complex list items.
36 |
37 | Let's even style a table:
38 |
39 | | Wrestler | Origin | Finisher |
40 | | ----------------------- | ------------ | ------------------ |
41 | | Bret "The Hitman" Hart | Calgary, AB | Sharpshooter |
42 | | Stone Cold Steve Austin | Austin, TX | Stone Cold Stunner |
43 | | Randy Savage | Sarasota, FL | Elbow Drop |
44 | | Vader | Boulder, CO | Vader Bomb |
45 | | Razor Ramon | Chuluota, FL | Razor's Edge |
46 |
47 | Finally, a figure with a caption:
48 |
49 |
50 |
54 |
55 | Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of
56 | classical Latin literature from 45 BC, making it over 2000 years old.
57 |
58 |
59 |
60 | And that's the end of our little demo.
61 |
--------------------------------------------------------------------------------
/demo/next.config.js:
--------------------------------------------------------------------------------
1 | const withMDX = require('@next/mdx')()
2 |
3 | module.exports = withMDX({
4 | pageExtensions: ['js', 'jsx', 'mdx'],
5 | })
6 |
--------------------------------------------------------------------------------
/demo/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 | import React from 'react'
3 |
4 | export default function App({ Component, pageProps }) {
5 | return
6 | }
7 |
--------------------------------------------------------------------------------
/demo/pages/dark.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
3 |
4 | export default function Index() {
5 | return (
6 |
7 |
8 |
Tailwind CSS Typography
9 |
10 |
11 |
12 |
13 | Are you happy now?
14 |
15 |
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export const config = {
23 | unstable_runtimeJS: false,
24 | }
25 |
--------------------------------------------------------------------------------
/demo/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import MarkdownSample from '../components/MarkdownSample.mdx'
3 |
4 | export default function Index() {
5 | return (
6 |
7 |
8 |
Tailwind CSS Typography
9 |
10 |
11 |
12 | Tailwind CSS Typography
13 |
14 |
15 |
31 |
32 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | )
56 | }
57 |
58 | export const config = {
59 | unstable_runtimeJS: false,
60 | }
61 |
--------------------------------------------------------------------------------
/demo/pages/list-items.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
3 |
4 | export default function Index() {
5 | return (
6 |
7 |
8 |
Tailwind CSS Typography
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
prose-sm
23 |
It's important to cover all of these use cases for a few reasons:
24 |
25 | - We want everything to look good out of the box.
26 | - Really just the first reason, that's the whole point of the plugin.
27 | -
28 | Here's a third pretend reason though a list with three items looks more realistic than
29 | a list with two items.
30 |
31 | - We want everything to look good out of the box.
32 | - Really just the first reason, that's the whole point of the plugin.
33 |
34 |
Now we're going to try out another header style.
35 |
36 | - So here is the first item in this list.
37 | - In this example we're keeping the items short.
38 | - Later, we'll use longer, more complex list items.
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
prose-base
55 |
It's important to cover all of these use cases for a few reasons:
56 |
57 | - We want everything to look good out of the box.
58 | - Really just the first reason, that's the whole point of the plugin.
59 | -
60 | Here's a third pretend reason though a list with three items looks more realistic than
61 | a list with two items.
62 |
63 | - We want everything to look good out of the box.
64 | - Really just the first reason, that's the whole point of the plugin.
65 |
66 |
Now we're going to try out another header style.
67 |
68 | - So here is the first item in this list.
69 | - In this example we're keeping the items short.
70 | - Later, we'll use longer, more complex list items.
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
prose-lg
87 |
It's important to cover all of these use cases for a few reasons:
88 |
89 | - We want everything to look good out of the box.
90 | - Really just the first reason, that's the whole point of the plugin.
91 | -
92 | Here's a third pretend reason though a list with three items looks more realistic than
93 | a list with two items.
94 |
95 | - We want everything to look good out of the box.
96 | - Really just the first reason, that's the whole point of the plugin.
97 |
98 |
Now we're going to try out another header style.
99 |
100 | - So here is the first item in this list.
101 | - In this example we're keeping the items short.
102 | - Later, we'll use longer, more complex list items.
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
prose-xl
119 |
It's important to cover all of these use cases for a few reasons:
120 |
121 | - We want everything to look good out of the box.
122 | - Really just the first reason, that's the whole point of the plugin.
123 | -
124 | Here's a third pretend reason though a list with three items looks more realistic than
125 | a list with two items.
126 |
127 | - We want everything to look good out of the box.
128 | - Really just the first reason, that's the whole point of the plugin.
129 |
130 |
Now we're going to try out another header style.
131 |
132 | - So here is the first item in this list.
133 | - In this example we're keeping the items short.
134 | - Later, we'll use longer, more complex list items.
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
prose-2xl
151 |
It's important to cover all of these use cases for a few reasons:
152 |
153 | - We want everything to look good out of the box.
154 | - Really just the first reason, that's the whole point of the plugin.
155 | -
156 | Here's a third pretend reason though a list with three items looks more realistic than
157 | a list with two items.
158 |
159 | - We want everything to look good out of the box.
160 | - Really just the first reason, that's the whole point of the plugin.
161 |
162 |
Now we're going to try out another header style.
163 |
164 | - So here is the first item in this list.
165 | - In this example we're keeping the items short.
166 | - Later, we'll use longer, more complex list items.
167 |
168 |
169 |
170 |
171 | )
172 | }
173 |
174 | export const config = {
175 | unstable_runtimeJS: false,
176 | }
177 |
--------------------------------------------------------------------------------
/demo/pages/themes.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
3 |
4 | export default function Index() {
5 | return (
6 |
7 |
8 |
Tailwind CSS Typography
9 |
10 |
11 |
12 |
13 | Slate
14 |
15 |
16 |
17 |
18 |
19 | Slate
20 |
21 |
22 |
23 |
24 |
25 | Gray
26 |
27 |
28 |
29 |
30 |
31 | Gray
32 |
33 |
34 |
35 |
36 |
37 | Zinc
38 |
39 |
40 |
41 |
42 |
43 | Zinc
44 |
45 |
46 |
47 |
48 |
49 | Neutral
50 |
51 |
52 |
53 |
54 |
55 | Neutral
56 |
57 |
58 |
59 |
60 |
61 | Stone
62 |
63 |
64 |
65 |
66 |
67 | Stone
68 |
69 |
70 |
71 |
72 |
73 | Red Links
74 |
75 |
76 |
77 |
78 |
79 | Red Links
80 |
81 |
82 |
83 |
84 |
85 | Orange Links
86 |
87 |
88 |
89 |
90 |
91 | Orange Links
92 |
93 |
94 |
95 |
96 |
97 | Amber Links
98 |
99 |
100 |
101 |
102 |
103 | Amber Links
104 |
105 |
106 |
107 |
108 |
109 | Yellow Links
110 |
111 |
112 |
113 |
114 |
115 | Yellow Links
116 |
117 |
118 |
119 |
120 |
121 | Lime Links
122 |
123 |
124 |
125 |
126 |
127 | Lime Links
128 |
129 |
130 |
131 |
132 |
133 | Green Links
134 |
135 |
136 |
137 |
138 |
139 | Green Links
140 |
141 |
142 |
143 |
144 |
145 | Emerald Links
146 |
147 |
148 |
149 |
150 |
151 | Emerald Links
152 |
153 |
154 |
155 |
156 |
157 | Teal Links
158 |
159 |
160 |
161 |
162 |
163 | Teal Links
164 |
165 |
166 |
167 |
168 |
169 | Cyan Links
170 |
171 |
172 |
173 |
174 |
175 | Cyan Links
176 |
177 |
178 |
179 |
180 |
181 | Sky Links
182 |
183 |
184 |
185 |
186 |
187 | Sky Links
188 |
189 |
190 |
191 |
192 |
193 | Blue Links
194 |
195 |
196 |
197 |
198 |
199 | Blue Links
200 |
201 |
202 |
203 |
204 |
205 | Indigo Links
206 |
207 |
208 |
209 |
210 |
211 | Indigo Links
212 |
213 |
214 |
215 |
216 |
217 | Violet Links
218 |
219 |
220 |
221 |
222 |
223 | Violet Links
224 |
225 |
226 |
227 |
228 |
229 | Purple Links
230 |
231 |
232 |
233 |
234 |
235 | Purple Links
236 |
237 |
238 |
239 |
240 |
241 | Fuchsia Links
242 |
243 |
244 |
245 |
246 |
247 | Fuchsia Links
248 |
249 |
250 |
251 |
252 |
253 | Pink Links
254 |
255 |
256 |
257 |
258 |
259 | Pink Links
260 |
261 |
262 |
263 |
264 |
265 | Rose Links
266 |
267 |
268 |
269 |
270 |
271 | Rose Links
272 |
273 |
274 |
275 |
276 |
277 | )
278 | }
279 |
280 | export const config = {
281 | unstable_runtimeJS: false,
282 | }
283 |
--------------------------------------------------------------------------------
/demo/pages/variants.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import MarkdownSampleShort from '../components/MarkdownSampleShort.mdx'
3 |
4 | export default function Variants() {
5 | return (
6 |
7 |
8 |
Tailwind CSS Typography
9 |
10 |
11 |
12 | Tailwind CSS Typography
13 |
14 |
15 |
31 |
32 |
47 |
48 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | )
64 | }
65 |
--------------------------------------------------------------------------------
/demo/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: { config: `${__dirname}/tailwind.config.js` },
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/demo/public/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tailwindlabs/tailwindcss-typography/632970e3ce6fc10d1bfd8fb46cc9083d0d32986d/demo/public/favicon-16x16.png
--------------------------------------------------------------------------------
/demo/public/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tailwindlabs/tailwindcss-typography/632970e3ce6fc10d1bfd8fb46cc9083d0d32986d/demo/public/favicon-32x32.png
--------------------------------------------------------------------------------
/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tailwindlabs/tailwindcss-typography/632970e3ce6fc10d1bfd8fb46cc9083d0d32986d/demo/public/favicon.ico
--------------------------------------------------------------------------------
/demo/tailwind.config.js:
--------------------------------------------------------------------------------
1 | const mdx = require('@mdx-js/mdx')
2 |
3 | module.exports = {
4 | content: ['./demo/pages/**/*.{js,mdx}', './demo/components/**/*.{js,mdx}'],
5 | transform: {
6 | mdx: (content) => mdx.sync(content),
7 | },
8 | theme: {
9 | extend: {
10 | colors: {
11 | red: { 1000: '#530F0F' },
12 | orange: { 1000: '#521C0B' },
13 | amber: { 1000: '#54240B' },
14 | yellow: { 1000: '#4D280A' },
15 | lime: { 1000: '#213708' },
16 | green: { 1000: '#0F3D23' },
17 | emerald: { 1000: '#02392C' },
18 | teal: { 1000: '#073937' },
19 | cyan: { 1000: '#09364A' },
20 | sky: { 1000: '#072F49' },
21 | blue: { 1000: '#17275C' },
22 | indigo: { 1000: '#1F1C53' },
23 | violet: { 1000: '#2F1265' },
24 | purple: { 1000: '#3D1061' },
25 | fuchsia: { 1000: '#4C0C4F' },
26 | pink: { 1000: '#4E0B26' },
27 | rose: { 1000: '#4E071B' },
28 | },
29 | },
30 | },
31 | variants: {},
32 | plugins: [require('../src/index.js')],
33 | }
34 |
--------------------------------------------------------------------------------
/jest/customMatchers.js:
--------------------------------------------------------------------------------
1 | const prettier = require('prettier')
2 | const { diff } = require('jest-diff')
3 |
4 | function format(input) {
5 | return prettier.format(input, {
6 | parser: 'css',
7 | printWidth: 100,
8 | })
9 | }
10 |
11 | expect.extend({
12 | // Compare two CSS strings with all whitespace removed
13 | // This is probably naive but it's fast and works well enough.
14 | toMatchCss(received, argument) {
15 | function stripped(str) {
16 | return str.replace(/\s/g, '').replace(/;/g, '')
17 | }
18 |
19 | const options = {
20 | comment: 'stripped(received) === stripped(argument)',
21 | isNot: this.isNot,
22 | promise: this.promise,
23 | }
24 |
25 | const pass = stripped(received) === stripped(argument)
26 |
27 | const message = pass
28 | ? () => {
29 | return (
30 | this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
31 | '\n\n' +
32 | `Expected: not ${this.utils.printExpected(format(received))}\n` +
33 | `Received: ${this.utils.printReceived(format(argument))}`
34 | )
35 | }
36 | : () => {
37 | const actual = format(received)
38 | const expected = format(argument)
39 |
40 | const diffString = diff(expected, actual, {
41 | expand: this.expand,
42 | })
43 |
44 | return (
45 | this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
46 | '\n\n' +
47 | (diffString && diffString.includes('- Expect')
48 | ? `Difference:\n\n${diffString}`
49 | : `Expected: ${this.utils.printExpected(expected)}\n` +
50 | `Received: ${this.utils.printReceived(actual)}`)
51 | )
52 | }
53 |
54 | return { actual: received, message, pass }
55 | },
56 | toIncludeCss(received, argument) {
57 | const options = {
58 | comment: 'stripped(received).includes(stripped(argument))',
59 | isNot: this.isNot,
60 | promise: this.promise,
61 | }
62 |
63 | const actual = format(received)
64 | const expected = format(argument)
65 |
66 | const pass = actual.includes(expected)
67 |
68 | const message = pass
69 | ? () => {
70 | return (
71 | this.utils.matcherHint('toIncludeCss', undefined, undefined, options) +
72 | '\n\n' +
73 | `Expected: not ${this.utils.printExpected(format(received))}\n` +
74 | `Received: ${this.utils.printReceived(format(argument))}`
75 | )
76 | }
77 | : () => {
78 | const diffString = diff(expected, actual, {
79 | expand: this.expand,
80 | })
81 |
82 | return (
83 | this.utils.matcherHint('toIncludeCss', undefined, undefined, options) +
84 | '\n\n' +
85 | (diffString && diffString.includes('- Expect')
86 | ? `Difference:\n\n${diffString}`
87 | : `Expected: ${this.utils.printExpected(expected)}\n` +
88 | `Received: ${this.utils.printReceived(actual)}`)
89 | )
90 | }
91 |
92 | return { actual: received, message, pass }
93 | },
94 | })
95 |
96 | expect.extend({
97 | // Compare two CSS strings with all whitespace removed
98 | // This is probably naive but it's fast and works well enough.
99 | toMatchFormattedCss(received, argument) {
100 | function format(input) {
101 | return prettier.format(input.replace(/\n/g, ''), {
102 | parser: 'css',
103 | printWidth: 100,
104 | })
105 | }
106 | const options = {
107 | comment: 'stripped(received) === stripped(argument)',
108 | isNot: this.isNot,
109 | promise: this.promise,
110 | }
111 |
112 | let formattedReceived = format(received)
113 | let formattedArgument = format(argument)
114 |
115 | const pass = formattedReceived === formattedArgument
116 |
117 | const message = pass
118 | ? () => {
119 | return (
120 | this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
121 | '\n\n' +
122 | `Expected: not ${this.utils.printExpected(formattedReceived)}\n` +
123 | `Received: ${this.utils.printReceived(formattedArgument)}`
124 | )
125 | }
126 | : () => {
127 | const actual = formattedReceived
128 | const expected = formattedArgument
129 |
130 | const diffString = diff(expected, actual, {
131 | expand: this.expand,
132 | })
133 |
134 | return (
135 | this.utils.matcherHint('toMatchCss', undefined, undefined, options) +
136 | '\n\n' +
137 | (diffString && diffString.includes('- Expect')
138 | ? `Difference:\n\n${diffString}`
139 | : `Expected: ${this.utils.printExpected(expected)}\n` +
140 | `Received: ${this.utils.printReceived(actual)}`)
141 | )
142 | }
143 |
144 | return { actual: received, message, pass }
145 | },
146 | })
147 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@tailwindcss/typography",
3 | "version": "0.5.16",
4 | "description": "A Tailwind CSS plugin for automatically styling plain HTML content with beautiful typographic defaults.",
5 | "main": "src/index.js",
6 | "types": "src/index.d.ts",
7 | "files": [
8 | "src/*.js",
9 | "src/*.d.ts",
10 | "dist/"
11 | ],
12 | "repository": "https://github.com/tailwindlabs/tailwindcss-typography",
13 | "license": "MIT",
14 | "publishConfig": {
15 | "access": "public"
16 | },
17 | "prettier": {
18 | "printWidth": 100,
19 | "semi": false,
20 | "singleQuote": true,
21 | "trailingComma": "es5"
22 | },
23 | "scripts": {
24 | "test": "jest",
25 | "dev": "next dev demo",
26 | "build": "next build demo",
27 | "export": "next export demo",
28 | "start": "next start demo",
29 | "release-channel": "node ./scripts/release-channel.js",
30 | "release-notes": "node ./scripts/release-notes.js"
31 | },
32 | "peerDependencies": {
33 | "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
34 | },
35 | "devDependencies": {
36 | "@mdx-js/loader": "^1.0.19",
37 | "@mdx-js/mdx": "^1.6.6",
38 | "@next/mdx": "^8.1.0",
39 | "autoprefixer": "^10.2.1",
40 | "highlight.js": "^10.4.1",
41 | "jest": "^29.7.0",
42 | "jest-diff": "^27.3.1",
43 | "next": "^12.0.1",
44 | "postcss": "^8.2.3",
45 | "prettier": "^2.1.2",
46 | "react": "^17.0.2",
47 | "react-dom": "^17.0.2",
48 | "tailwindcss": "^3.2.2"
49 | },
50 | "dependencies": {
51 | "lodash.castarray": "^4.4.0",
52 | "lodash.isplainobject": "^4.0.6",
53 | "lodash.merge": "^4.6.2",
54 | "postcss-selector-parser": "6.0.10"
55 | },
56 | "jest": {
57 | "setupFilesAfterEnv": [
58 | "/jest/customMatchers.js"
59 | ]
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/scripts/release-channel.js:
--------------------------------------------------------------------------------
1 | // Given a version, figure out what the release channel is so that we can publish to the correct
2 | // channel on npm.
3 | //
4 | // E.g.:
5 | //
6 | // 1.2.3 -> latest (default)
7 | // 0.0.0-insiders.ffaa88 -> insiders
8 | // 4.1.0-alpha.4 -> alpha
9 |
10 | let version =
11 | process.argv[2] || process.env.npm_package_version || require('../package.json').version
12 |
13 | let match = /\d+\.\d+\.\d+-(.*)\.\d+/g.exec(version)
14 | if (match) {
15 | console.log(match[1])
16 | } else {
17 | console.log('latest')
18 | }
19 |
--------------------------------------------------------------------------------
/scripts/release-notes.js:
--------------------------------------------------------------------------------
1 | // Given a version, figure out what the release notes are so that we can use this to pre-fill the
2 | // release notes on a GitHub release for the current version.
3 |
4 | let path = require('path')
5 | let fs = require('fs')
6 |
7 | let version =
8 | process.argv[2] || process.env.npm_package_version || require('../package.json').version
9 |
10 | let changelog = fs.readFileSync(path.resolve(__dirname, '..', 'CHANGELOG.md'), 'utf8')
11 | let match = new RegExp(
12 | `## \\[${version}\\] - (.*)\\n\\n([\\s\\S]*?)\\n(?:(?:##\\s)|(?:\\[))`,
13 | 'g'
14 | ).exec(changelog)
15 |
16 | if (match) {
17 | let [, date, notes] = match
18 | console.log(notes.trim())
19 | } else {
20 | console.log(`Placeholder release notes for version: v${version}`)
21 | }
22 |
--------------------------------------------------------------------------------
/src/index.d.ts:
--------------------------------------------------------------------------------
1 | declare function plugin(options?: Partial<{ className: string; target: 'modern' | 'legacy' }>): {
2 | handler: () => void
3 | }
4 |
5 | declare namespace plugin {
6 | const __isOptionsFunction: true
7 | }
8 |
9 | export = plugin
10 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | const plugin = require('tailwindcss/plugin')
2 | const merge = require('lodash.merge')
3 | const castArray = require('lodash.castarray')
4 | const styles = require('./styles')
5 | const { commonTrailingPseudos } = require('./utils')
6 |
7 | const computed = {
8 | // Reserved for future "magic properties", for example:
9 | // bulletColor: (color) => ({ 'ul > li::before': { backgroundColor: color } }),
10 | }
11 |
12 | function inWhere(selector, { className, modifier, prefix }) {
13 | let prefixedNot = prefix(`.not-${className}`).slice(1)
14 | let selectorPrefix = selector.startsWith('>')
15 | ? `${modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`} `
16 | : ''
17 |
18 | // Parse the selector, if every component ends in the same pseudo element(s) then move it to the end
19 | let [trailingPseudo, rebuiltSelector] = commonTrailingPseudos(selector)
20 |
21 | if (trailingPseudo) {
22 | return `:where(${selectorPrefix}${rebuiltSelector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))${trailingPseudo}`
23 | }
24 |
25 | return `:where(${selectorPrefix}${selector}):not(:where([class~="${prefixedNot}"],[class~="${prefixedNot}"] *))`
26 | }
27 |
28 | function isObject(value) {
29 | return typeof value === 'object' && value !== null
30 | }
31 |
32 | function configToCss(config = {}, { target, className, modifier, prefix }) {
33 | function updateSelector(k, v) {
34 | if (target === 'legacy') {
35 | return [k, v]
36 | }
37 |
38 | if (Array.isArray(v)) {
39 | return [k, v]
40 | }
41 |
42 | if (isObject(v)) {
43 | let nested = Object.values(v).some(isObject)
44 | if (nested) {
45 | return [
46 | inWhere(k, { className, modifier, prefix }),
47 | v,
48 | Object.fromEntries(Object.entries(v).map(([k, v]) => updateSelector(k, v))),
49 | ]
50 | }
51 |
52 | return [inWhere(k, { className, modifier, prefix }), v]
53 | }
54 |
55 | return [k, v]
56 | }
57 |
58 | return Object.fromEntries(
59 | Object.entries(
60 | merge(
61 | {},
62 | ...Object.keys(config)
63 | .filter((key) => computed[key])
64 | .map((key) => computed[key](config[key])),
65 | ...castArray(config.css || {})
66 | )
67 | ).map(([k, v]) => updateSelector(k, v))
68 | )
69 | }
70 |
71 | module.exports = plugin.withOptions(
72 | ({ className = 'prose', target = 'modern' } = {}) => {
73 | return function ({ addVariant, addComponents, theme, prefix }) {
74 | let modifiers = theme('typography')
75 |
76 | let options = { className, prefix }
77 |
78 | for (let [name, ...selectors] of [
79 | ['headings', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'th'],
80 | ['h1'],
81 | ['h2'],
82 | ['h3'],
83 | ['h4'],
84 | ['h5'],
85 | ['h6'],
86 | ['p'],
87 | ['a'],
88 | ['blockquote'],
89 | ['figure'],
90 | ['figcaption'],
91 | ['strong'],
92 | ['em'],
93 | ['kbd'],
94 | ['code'],
95 | ['pre'],
96 | ['ol'],
97 | ['ul'],
98 | ['li'],
99 | ['table'],
100 | ['thead'],
101 | ['tr'],
102 | ['th'],
103 | ['td'],
104 | ['img'],
105 | ['video'],
106 | ['hr'],
107 | ['lead', '[class~="lead"]'],
108 | ]) {
109 | selectors = selectors.length === 0 ? [name] : selectors
110 |
111 | let selector =
112 | target === 'legacy' ? selectors.map((selector) => `& ${selector}`) : selectors.join(', ')
113 |
114 | addVariant(
115 | `${className}-${name}`,
116 | target === 'legacy' ? selector : `& :is(${inWhere(selector, options)})`
117 | )
118 | }
119 |
120 | addComponents(
121 | Object.keys(modifiers).map((modifier) => ({
122 | [modifier === 'DEFAULT' ? `.${className}` : `.${className}-${modifier}`]: configToCss(
123 | modifiers[modifier],
124 | {
125 | target,
126 | className,
127 | modifier,
128 | prefix,
129 | }
130 | ),
131 | }))
132 | )
133 | }
134 | },
135 | () => {
136 | return {
137 | theme: { typography: styles },
138 | }
139 | }
140 | )
141 |
--------------------------------------------------------------------------------
/src/index.test.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const tailwind = require('tailwindcss')
3 | const postcss = require('postcss')
4 | const typographyPlugin = require('.')
5 |
6 | let html = String.raw
7 | let css = String.raw
8 |
9 | let vars = `
10 | --tw-border-spacing-x: 0;
11 | --tw-border-spacing-y: 0;
12 | --tw-translate-x: 0;
13 | --tw-translate-y: 0;
14 | --tw-rotate: 0;
15 | --tw-skew-x: 0;
16 | --tw-skew-y: 0;
17 | --tw-scale-x: 1;
18 | --tw-scale-y: 1;
19 | --tw-pan-x: ;
20 | --tw-pan-y: ;
21 | --tw-pinch-zoom: ;
22 | --tw-scroll-snap-strictness: proximity;
23 | --tw-ordinal: ;
24 | --tw-slashed-zero: ;
25 | --tw-numeric-figure: ;
26 | --tw-numeric-spacing: ;
27 | --tw-numeric-fraction: ;
28 | --tw-ring-inset: ;
29 | --tw-ring-offset-width: 0px;
30 | --tw-ring-offset-color: #fff;
31 | --tw-ring-color: rgb(59 130 246 / 0.5);
32 | --tw-ring-offset-shadow: 0 0 #0000;
33 | --tw-ring-shadow: 0 0 #0000;
34 | --tw-shadow: 0 0 #0000;
35 | --tw-shadow-colored: 0 0 #0000;
36 | --tw-blur: ;
37 | --tw-brightness: ;
38 | --tw-contrast: ;
39 | --tw-grayscale: ;
40 | --tw-hue-rotate: ;
41 | --tw-invert: ;
42 | --tw-saturate: ;
43 | --tw-sepia: ;
44 | --tw-drop-shadow: ;
45 | --tw-backdrop-blur: ;
46 | --tw-backdrop-brightness: ;
47 | --tw-backdrop-contrast: ;
48 | --tw-backdrop-grayscale: ;
49 | --tw-backdrop-hue-rotate: ;
50 | --tw-backdrop-invert: ;
51 | --tw-backdrop-opacity: ;
52 | --tw-backdrop-saturate: ;
53 | --tw-backdrop-sepia: ;
54 | `
55 | let defaults = css`
56 | *,
57 | ::before,
58 | ::after {
59 | ${vars}
60 | }
61 | ::backdrop {
62 | ${vars}
63 | }
64 | `
65 |
66 | function run(config, plugin = tailwind) {
67 | let { currentTestName } = expect.getState()
68 | config = {
69 | ...{ plugins: [typographyPlugin], corePlugins: { preflight: false } },
70 | ...config,
71 | }
72 |
73 | return postcss(plugin(config)).process(
74 | ['@tailwind base;', '@tailwind components;', '@tailwind utilities'].join('\n'),
75 | {
76 | from: `${path.resolve(__filename)}?test=${currentTestName}`,
77 | }
78 | )
79 | }
80 |
81 | test('specificity is reduced with :where', async () => {
82 | let config = {
83 | content: [{ raw: html`` }],
84 | theme: {
85 | typography: {
86 | DEFAULT: {
87 | css: [
88 | {
89 | color: 'var(--tw-prose-body)',
90 | maxWidth: '65ch',
91 | '[class~="lead"]': {
92 | color: 'var(--tw-prose-lead)',
93 | },
94 | strong: {
95 | color: 'var(--tw-prose-bold)',
96 | fontWeight: '600',
97 | },
98 | 'ol[type="A"]': {
99 | listStyleType: 'upper-alpha',
100 | },
101 | 'blockquote p:first-of-type::before': {
102 | content: 'open-quote',
103 | },
104 | 'blockquote p:last-of-type::after': {
105 | content: 'close-quote',
106 | },
107 | 'h4 strong': {
108 | fontWeight: '700',
109 | },
110 | 'figure > *': {
111 | margin: 0,
112 | },
113 | 'ol > li::marker': {
114 | fontWeight: '400',
115 | color: 'var(--tw-prose-counters)',
116 | },
117 | '> ul > li p': {
118 | marginTop: '16px',
119 | marginBottom: '16px',
120 | },
121 | 'code::before': {
122 | content: '"`"',
123 | },
124 | 'code::after': {
125 | content: '"`"',
126 | },
127 | },
128 | ],
129 | },
130 | },
131 | },
132 | }
133 |
134 | return run(config).then((result) => {
135 | expect(result.css).toMatchFormattedCss(
136 | css`
137 | ${defaults}
138 |
139 | .prose {
140 | color: var(--tw-prose-body);
141 | max-width: 65ch;
142 | }
143 | .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
144 | color: var(--tw-prose-lead);
145 | }
146 | .prose :where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
147 | color: var(--tw-prose-bold);
148 | font-weight: 600;
149 | }
150 | .prose :where(ol[type='A']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
151 | list-style-type: upper-alpha;
152 | }
153 | .prose
154 | :where(blockquote p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose']
155 | *))::before {
156 | content: open-quote;
157 | }
158 | .prose
159 | :where(blockquote p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose']
160 | *))::after {
161 | content: close-quote;
162 | }
163 | .prose :where(h4 strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
164 | font-weight: 700;
165 | }
166 | .prose :where(figure > *):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
167 | margin: 0;
168 | }
169 | .prose :where(ol > li):not(:where([class~='not-prose'], [class~='not-prose'] *))::marker {
170 | font-weight: 400;
171 | color: var(--tw-prose-counters);
172 | }
173 | .prose
174 | :where(.prose > ul > li p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
175 | margin-top: 16px;
176 | margin-bottom: 16px;
177 | }
178 | .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::before {
179 | content: '`';
180 | }
181 | .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::after {
182 | content: '`';
183 | }
184 | `
185 | )
186 | })
187 | })
188 |
189 | test('variants', async () => {
190 | let config = {
191 | content: [{ raw: html`` }],
192 | theme: {
193 | typography: {
194 | DEFAULT: {
195 | css: [
196 | {
197 | color: 'red',
198 | p: {
199 | color: 'lime',
200 | },
201 | '> ul > li': {
202 | color: 'purple',
203 | },
204 | },
205 | ],
206 | },
207 | lg: {
208 | css: {
209 | color: 'green',
210 | p: {
211 | color: 'tomato',
212 | },
213 | '> ul > li': {
214 | color: 'blue',
215 | },
216 | },
217 | },
218 | xl: {
219 | css: {
220 | color: 'yellow',
221 | '> ul > li': {
222 | color: 'hotpink',
223 | },
224 | },
225 | },
226 | },
227 | },
228 | }
229 |
230 | return run(config).then((result) => {
231 | expect(result.css).toMatchFormattedCss(
232 | css`
233 | ${defaults}
234 |
235 | .hover\:prose-lg:hover {
236 | color: green;
237 | }
238 | .hover\:prose-lg:hover :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
239 | color: tomato;
240 | }
241 | .hover\:prose-lg:hover
242 | :where(.hover\:prose-lg:hover
243 | > ul
244 | > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
245 | color: blue;
246 | }
247 | @media (min-width: 640px) {
248 | .sm\:prose {
249 | color: red;
250 | }
251 | .sm\:prose :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
252 | color: lime;
253 | }
254 | .sm\:prose
255 | :where(.sm\:prose > ul > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
256 | color: purple;
257 | }
258 | }
259 | @media (min-width: 1024px) {
260 | .lg\:prose-lg {
261 | color: green;
262 | }
263 | .lg\:prose-lg :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
264 | color: tomato;
265 | }
266 | .lg\:prose-lg
267 | :where(.lg\:prose-lg > ul > li):not(:where([class~='not-prose'], [class~='not-prose']
268 | *)) {
269 | color: blue;
270 | }
271 | }
272 | `
273 | )
274 | })
275 | })
276 |
277 | test('modifiers', async () => {
278 | let config = {
279 | content: [{ raw: html`` }],
280 | theme: {
281 | typography: {
282 | DEFAULT: {
283 | css: [
284 | {
285 | color: 'var(--tw-prose-body)',
286 | maxWidth: '65ch',
287 | '[class~="lead"]': {
288 | color: 'var(--tw-prose-lead)',
289 | },
290 | strong: {
291 | color: 'var(--tw-prose-bold)',
292 | fontWeight: '600',
293 | },
294 | 'ol[type="A"]': {
295 | listStyleType: 'upper-alpha',
296 | },
297 | 'blockquote p:first-of-type::before': {
298 | content: 'open-quote',
299 | },
300 | 'blockquote p:last-of-type::after': {
301 | content: 'close-quote',
302 | },
303 | 'h4 strong': {
304 | fontWeight: '700',
305 | },
306 | 'figure > *': {
307 | margin: 0,
308 | },
309 | 'ol > li::marker': {
310 | fontWeight: '400',
311 | color: 'var(--tw-prose-counters)',
312 | },
313 | 'code::before': {
314 | content: '"`"',
315 | },
316 | 'code::after': {
317 | content: '"`"',
318 | },
319 | },
320 | ],
321 | },
322 | lg: {
323 | css: [
324 | {
325 | fontSize: '18px',
326 | lineHeight: '1.75',
327 | p: {
328 | marginTop: '24px',
329 | marginBottom: '24px',
330 | },
331 | '[class~="lead"]': {
332 | fontSize: '22px',
333 | },
334 | blockquote: {
335 | marginTop: '40px',
336 | marginBottom: '40px',
337 | },
338 | '> ul > li': {
339 | paddingLeft: '12px',
340 | },
341 | h1: {
342 | fontSize: '48px',
343 | marginTop: '0',
344 | marginBottom: '40px',
345 | },
346 | h2: {
347 | fontSize: '30px',
348 | marginTop: '56px',
349 | marginBottom: '32px',
350 | },
351 | h3: {
352 | fontSize: '24px',
353 | marginTop: '40px',
354 | marginBottom: '16px',
355 | },
356 | },
357 | ],
358 | },
359 | },
360 | },
361 | }
362 |
363 | return run(config).then((result) => {
364 | expect(result.css).toMatchFormattedCss(
365 | css`
366 | ${defaults}
367 |
368 | .prose {
369 | color: var(--tw-prose-body);
370 | max-width: 65ch;
371 | }
372 | .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
373 | color: var(--tw-prose-lead);
374 | }
375 | .prose :where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
376 | color: var(--tw-prose-bold);
377 | font-weight: 600;
378 | }
379 | .prose :where(ol[type='A']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
380 | list-style-type: upper-alpha;
381 | }
382 | .prose
383 | :where(blockquote p:first-of-type):not(:where([class~='not-prose'], [class~='not-prose']
384 | *))::before {
385 | content: open-quote;
386 | }
387 | .prose
388 | :where(blockquote p:last-of-type):not(:where([class~='not-prose'], [class~='not-prose']
389 | *))::after {
390 | content: close-quote;
391 | }
392 | .prose :where(h4 strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
393 | font-weight: 700;
394 | }
395 | .prose :where(figure > *):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
396 | margin: 0;
397 | }
398 | .prose :where(ol > li):not(:where([class~='not-prose'], [class~='not-prose'] *))::marker {
399 | font-weight: 400;
400 | color: var(--tw-prose-counters);
401 | }
402 | .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::before {
403 | content: '`';
404 | }
405 | .prose :where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))::after {
406 | content: '`';
407 | }
408 | .prose-lg {
409 | font-size: 18px;
410 | line-height: 1.75;
411 | }
412 | .prose-lg :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
413 | margin-top: 24px;
414 | margin-bottom: 24px;
415 | }
416 | .prose-lg
417 | :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
418 | font-size: 22px;
419 | }
420 | .prose-lg :where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
421 | margin-top: 40px;
422 | margin-bottom: 40px;
423 | }
424 | .prose-lg
425 | :where(.prose-lg > ul > li):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
426 | padding-left: 12px;
427 | }
428 | .prose-lg :where(h1):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
429 | font-size: 48px;
430 | margin-top: 0;
431 | margin-bottom: 40px;
432 | }
433 | .prose-lg :where(h2):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
434 | font-size: 30px;
435 | margin-top: 56px;
436 | margin-bottom: 32px;
437 | }
438 | .prose-lg :where(h3):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
439 | font-size: 24px;
440 | margin-top: 40px;
441 | margin-bottom: 16px;
442 | }
443 | `
444 | )
445 | })
446 | })
447 |
448 | test('legacy target', async () => {
449 | let config = {
450 | plugins: [typographyPlugin({ target: 'legacy' })],
451 | content: [
452 | { raw: html`` },
453 | ],
454 | theme: {
455 | typography: {
456 | DEFAULT: {
457 | css: [
458 | {
459 | color: 'var(--tw-prose-body)',
460 | maxWidth: '65ch',
461 | '[class~="lead"]': {
462 | color: 'var(--tw-prose-lead)',
463 | },
464 | strong: {
465 | color: 'var(--tw-prose-bold)',
466 | fontWeight: '600',
467 | },
468 | 'ol[type="A"]': {
469 | listStyleType: 'upper-alpha',
470 | },
471 | 'blockquote p:first-of-type::before': {
472 | content: 'open-quote',
473 | },
474 | 'blockquote p:last-of-type::after': {
475 | content: 'close-quote',
476 | },
477 | 'h4 strong': {
478 | fontWeight: '700',
479 | },
480 | 'figure > *': {
481 | margin: 0,
482 | },
483 | 'ol > li::marker': {
484 | fontWeight: '400',
485 | color: 'var(--tw-prose-counters)',
486 | },
487 | 'code::before': {
488 | content: '"`"',
489 | },
490 | 'code::after': {
491 | content: '"`"',
492 | },
493 | },
494 | ],
495 | },
496 | },
497 | },
498 | }
499 |
500 | return run(config).then((result) => {
501 | expect(result.css).toMatchFormattedCss(
502 | css`
503 | ${defaults}
504 |
505 | .prose {
506 | color: var(--tw-prose-body);
507 | max-width: 65ch;
508 | }
509 | .prose [class~='lead'] {
510 | color: var(--tw-prose-lead);
511 | }
512 | .prose strong {
513 | color: var(--tw-prose-bold);
514 | font-weight: 600;
515 | }
516 | .prose ol[type='A'] {
517 | list-style-type: upper-alpha;
518 | }
519 | .prose blockquote p:first-of-type::before {
520 | content: open-quote;
521 | }
522 | .prose blockquote p:last-of-type::after {
523 | content: close-quote;
524 | }
525 | .prose h4 strong {
526 | font-weight: 700;
527 | }
528 | .prose figure > * {
529 | margin: 0;
530 | }
531 | .prose ol > li::marker {
532 | font-weight: 400;
533 | color: var(--tw-prose-counters);
534 | }
535 | .prose code::before {
536 | content: '`';
537 | }
538 | .prose code::after {
539 | content: '`';
540 | }
541 | .prose-headings\:text-ellipsis h1 {
542 | text-overflow: ellipsis;
543 | }
544 | .prose-headings\:text-ellipsis h2 {
545 | text-overflow: ellipsis;
546 | }
547 | .prose-headings\:text-ellipsis h3 {
548 | text-overflow: ellipsis;
549 | }
550 | .prose-headings\:text-ellipsis h4 {
551 | text-overflow: ellipsis;
552 | }
553 | .prose-headings\:text-ellipsis h5 {
554 | text-overflow: ellipsis;
555 | }
556 | .prose-headings\:text-ellipsis h6 {
557 | text-overflow: ellipsis;
558 | }
559 | .prose-headings\:text-ellipsis th {
560 | text-overflow: ellipsis;
561 | }
562 | .prose-h1\:text-center h1 {
563 | text-align: center;
564 | }
565 | `
566 | )
567 | })
568 | })
569 |
570 | test('custom class name', async () => {
571 | let config = {
572 | plugins: [typographyPlugin({ className: 'markdown' })],
573 | content: [{ raw: html`` }],
574 | theme: {
575 | typography: {
576 | DEFAULT: {
577 | css: [
578 | {
579 | color: 'var(--tw-prose-body)',
580 | maxWidth: '65ch',
581 | '[class~="lead"]': {
582 | color: 'var(--tw-prose-lead)',
583 | },
584 | strong: {
585 | color: 'var(--tw-prose-bold)',
586 | fontWeight: '600',
587 | },
588 | 'ol[type="A"]': {
589 | listStyleType: 'upper-alpha',
590 | },
591 | 'blockquote p:first-of-type::before': {
592 | content: 'open-quote',
593 | },
594 | 'blockquote p:last-of-type::after': {
595 | content: 'close-quote',
596 | },
597 | 'h4 strong': {
598 | fontWeight: '700',
599 | },
600 | 'figure > *': {
601 | margin: 0,
602 | },
603 | 'ol > li::marker': {
604 | fontWeight: '400',
605 | color: 'var(--tw-prose-counters)',
606 | },
607 | 'code::before': {
608 | content: '"`"',
609 | },
610 | 'code::after': {
611 | content: '"`"',
612 | },
613 | },
614 | ],
615 | },
616 | },
617 | },
618 | }
619 |
620 | return run(config).then((result) => {
621 | expect(result.css).toMatchFormattedCss(
622 | css`
623 | ${defaults}
624 |
625 | .markdown {
626 | color: var(--tw-prose-body);
627 | max-width: 65ch;
628 | }
629 | .markdown
630 | :where([class~='lead']):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
631 | color: var(--tw-prose-lead);
632 | }
633 | .markdown :where(strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
634 | color: var(--tw-prose-bold);
635 | font-weight: 600;
636 | }
637 | .markdown
638 | :where(ol[type='A']):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
639 | list-style-type: upper-alpha;
640 | }
641 | .markdown
642 | :where(blockquote
643 | p:first-of-type):not(:where([class~='not-markdown'], [class~='not-markdown']
644 | *))::before {
645 | content: open-quote;
646 | }
647 | .markdown
648 | :where(blockquote
649 | p:last-of-type):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::after {
650 | content: close-quote;
651 | }
652 | .markdown
653 | :where(h4 strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
654 | font-weight: 700;
655 | }
656 | .markdown
657 | :where(figure > *):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
658 | margin: 0;
659 | }
660 | .markdown
661 | :where(ol > li):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::marker {
662 | font-weight: 400;
663 | color: var(--tw-prose-counters);
664 | }
665 | .markdown
666 | :where(code):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::before {
667 | content: '`';
668 | }
669 | .markdown
670 | :where(code):not(:where([class~='not-markdown'], [class~='not-markdown'] *))::after {
671 | content: '`';
672 | }
673 | `
674 | )
675 | })
676 | })
677 |
678 | test('element variants', async () => {
679 | let config = {
680 | content: [
681 | {
682 | raw: html``,
714 | },
715 | ],
716 | theme: {
717 | typography: {
718 | DEFAULT: {
719 | css: [
720 | {
721 | color: 'var(--tw-prose-body)',
722 | '[class~="lead"]': {
723 | color: 'var(--tw-prose-lead)',
724 | },
725 | strong: {
726 | color: 'var(--tw-prose-bold)',
727 | fontWeight: '600',
728 | },
729 | 'h4 strong': {
730 | fontWeight: '700',
731 | },
732 | },
733 | ],
734 | },
735 | },
736 | },
737 | }
738 | return run(config).then((result) => {
739 | expect(result.css).toMatchFormattedCss(
740 | css`
741 | ${defaults}
742 |
743 | .prose {
744 | color: var(--tw-prose-body);
745 | }
746 | .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
747 | color: var(--tw-prose-lead);
748 | }
749 | .prose :where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
750 | color: var(--tw-prose-bold);
751 | font-weight: 600;
752 | }
753 | .prose :where(h4 strong):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
754 | font-weight: 700;
755 | }
756 | .prose-headings\:underline
757 | :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~='not-prose'], [class~='not-prose']
758 | *))) {
759 | text-decoration-line: underline;
760 | }
761 | .prose-h1\:text-3xl
762 | :is(:where(h1):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
763 | font-size: 1.875rem;
764 | line-height: 2.25rem;
765 | }
766 | .prose-h2\:text-2xl
767 | :is(:where(h2):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
768 | font-size: 1.5rem;
769 | line-height: 2rem;
770 | }
771 | .prose-h3\:text-xl
772 | :is(:where(h3):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
773 | font-size: 1.25rem;
774 | line-height: 1.75rem;
775 | }
776 | .prose-h4\:text-lg
777 | :is(:where(h4):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
778 | font-size: 1.125rem;
779 | line-height: 1.75rem;
780 | }
781 | .prose-p\:text-gray-700
782 | :is(:where(p):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
783 | --tw-text-opacity: 1;
784 | color: rgb(55 65 81 / var(--tw-text-opacity));
785 | }
786 | .prose-a\:font-bold
787 | :is(:where(a):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
788 | font-weight: 700;
789 | }
790 | .prose-blockquote\:italic
791 | :is(:where(blockquote):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
792 | font-style: italic;
793 | }
794 | .prose-figure\:mx-auto
795 | :is(:where(figure):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
796 | margin-left: auto;
797 | margin-right: auto;
798 | }
799 | .prose-figcaption\:opacity-75
800 | :is(:where(figcaption):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
801 | opacity: 0.75;
802 | }
803 | .prose-strong\:font-medium
804 | :is(:where(strong):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
805 | font-weight: 500;
806 | }
807 | .prose-em\:italic
808 | :is(:where(em):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
809 | font-style: italic;
810 | }
811 | .prose-kbd\:border-b-2
812 | :is(:where(kbd):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
813 | border-bottom-width: 2px;
814 | }
815 | .prose-code\:font-mono
816 | :is(:where(code):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
817 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
818 | 'Courier New', monospace;
819 | }
820 | .prose-pre\:font-mono
821 | :is(:where(pre):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
822 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
823 | 'Courier New', monospace;
824 | }
825 | .prose-ol\:pl-6 :is(:where(ol):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
826 | padding-left: 1.5rem;
827 | }
828 | .prose-ul\:pl-8 :is(:where(ul):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
829 | padding-left: 2rem;
830 | }
831 | .prose-li\:my-4 :is(:where(li):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
832 | margin-top: 1rem;
833 | margin-bottom: 1rem;
834 | }
835 | .prose-table\:my-8
836 | :is(:where(table):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
837 | margin-top: 2rem;
838 | margin-bottom: 2rem;
839 | }
840 | .prose-thead\:border-red-300
841 | :is(:where(thead):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
842 | --tw-border-opacity: 1;
843 | border-color: rgb(252 165 165 / var(--tw-border-opacity));
844 | }
845 | .prose-tr\:border-red-200
846 | :is(:where(tr):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
847 | --tw-border-opacity: 1;
848 | border-color: rgb(254 202 202 / var(--tw-border-opacity));
849 | }
850 | .prose-th\:text-left
851 | :is(:where(th):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
852 | text-align: left;
853 | }
854 | .prose-img\:rounded-lg
855 | :is(:where(img):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
856 | border-radius: 0.5rem;
857 | }
858 | .prose-video\:my-12
859 | :is(:where(video):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
860 | margin-top: 3rem;
861 | margin-bottom: 3rem;
862 | }
863 | .prose-hr\:border-t-2
864 | :is(:where(hr):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
865 | border-top-width: 2px;
866 | }
867 | .prose-lead\:italic
868 | :is(:where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
869 | font-style: italic;
870 | }
871 | `
872 | )
873 | })
874 | })
875 |
876 | test('element variants with custom class name', async () => {
877 | let config = {
878 | plugins: [typographyPlugin({ className: 'markdown' })],
879 | content: [
880 | {
881 | raw: html``,
913 | },
914 | ],
915 | theme: {
916 | typography: {
917 | DEFAULT: {
918 | css: [
919 | {
920 | color: 'var(--tw-prose-body)',
921 | '[class~="lead"]': {
922 | color: 'var(--tw-prose-lead)',
923 | },
924 | strong: {
925 | color: 'var(--tw-prose-bold)',
926 | fontWeight: '600',
927 | },
928 | 'h4 strong': {
929 | fontWeight: '700',
930 | },
931 | },
932 | ],
933 | },
934 | },
935 | },
936 | }
937 | return run(config).then((result) => {
938 | expect(result.css).toMatchFormattedCss(
939 | css`
940 | ${defaults}
941 |
942 | .markdown {
943 | color: var(--tw-prose-body);
944 | }
945 | .markdown
946 | :where([class~='lead']):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
947 | color: var(--tw-prose-lead);
948 | }
949 | .markdown :where(strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
950 | color: var(--tw-prose-bold);
951 | font-weight: 600;
952 | }
953 | .markdown
954 | :where(h4 strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *)) {
955 | font-weight: 700;
956 | }
957 | .markdown-headings\:underline
958 | :is(:where(h1, h2, h3, h4, h5, h6, th):not(:where([class~='not-markdown'], [class~='not-markdown']
959 | *))) {
960 | text-decoration-line: underline;
961 | }
962 | .markdown-h1\:text-3xl
963 | :is(:where(h1):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
964 | font-size: 1.875rem;
965 | line-height: 2.25rem;
966 | }
967 | .markdown-h2\:text-2xl
968 | :is(:where(h2):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
969 | font-size: 1.5rem;
970 | line-height: 2rem;
971 | }
972 | .markdown-h3\:text-xl
973 | :is(:where(h3):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
974 | font-size: 1.25rem;
975 | line-height: 1.75rem;
976 | }
977 | .markdown-h4\:text-lg
978 | :is(:where(h4):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
979 | font-size: 1.125rem;
980 | line-height: 1.75rem;
981 | }
982 | .markdown-p\:text-gray-700
983 | :is(:where(p):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
984 | --tw-text-opacity: 1;
985 | color: rgb(55 65 81 / var(--tw-text-opacity));
986 | }
987 | .markdown-a\:font-bold
988 | :is(:where(a):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
989 | font-weight: 700;
990 | }
991 | .markdown-blockquote\:italic
992 | :is(:where(blockquote):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
993 | font-style: italic;
994 | }
995 | .markdown-figure\:mx-auto
996 | :is(:where(figure):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
997 | margin-left: auto;
998 | margin-right: auto;
999 | }
1000 | .markdown-figcaption\:opacity-75
1001 | :is(:where(figcaption):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1002 | opacity: 0.75;
1003 | }
1004 | .markdown-strong\:font-medium
1005 | :is(:where(strong):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1006 | font-weight: 500;
1007 | }
1008 | .markdown-em\:italic
1009 | :is(:where(em):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1010 | font-style: italic;
1011 | }
1012 | .markdown-kbd\:border-b-2
1013 | :is(:where(kbd):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1014 | border-bottom-width: 2px;
1015 | }
1016 | .markdown-code\:font-mono
1017 | :is(:where(code):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1018 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
1019 | 'Courier New', monospace;
1020 | }
1021 | .markdown-pre\:font-mono
1022 | :is(:where(pre):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1023 | font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono',
1024 | 'Courier New', monospace;
1025 | }
1026 | .markdown-ol\:pl-6
1027 | :is(:where(ol):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1028 | padding-left: 1.5rem;
1029 | }
1030 | .markdown-ul\:pl-8
1031 | :is(:where(ul):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1032 | padding-left: 2rem;
1033 | }
1034 | .markdown-li\:my-4
1035 | :is(:where(li):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1036 | margin-top: 1rem;
1037 | margin-bottom: 1rem;
1038 | }
1039 | .markdown-table\:my-8
1040 | :is(:where(table):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1041 | margin-top: 2rem;
1042 | margin-bottom: 2rem;
1043 | }
1044 | .markdown-thead\:border-red-300
1045 | :is(:where(thead):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1046 | --tw-border-opacity: 1;
1047 | border-color: rgb(252 165 165 / var(--tw-border-opacity));
1048 | }
1049 | .markdown-tr\:border-red-200
1050 | :is(:where(tr):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1051 | --tw-border-opacity: 1;
1052 | border-color: rgb(254 202 202 / var(--tw-border-opacity));
1053 | }
1054 | .markdown-th\:text-left
1055 | :is(:where(th):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1056 | text-align: left;
1057 | }
1058 | .markdown-img\:rounded-lg
1059 | :is(:where(img):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1060 | border-radius: 0.5rem;
1061 | }
1062 | .markdown-video\:my-12
1063 | :is(:where(video):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1064 | margin-top: 3rem;
1065 | margin-bottom: 3rem;
1066 | }
1067 | .markdown-hr\:border-t-2
1068 | :is(:where(hr):not(:where([class~='not-markdown'], [class~='not-markdown'] *))) {
1069 | border-top-width: 2px;
1070 | }
1071 | .markdown-lead\:italic
1072 | :is(:where([class~='lead']):not(:where([class~='not-markdown'], [class~='not-markdown']
1073 | *))) {
1074 | font-style: italic;
1075 | }
1076 | `
1077 | )
1078 | })
1079 | })
1080 |
1081 | test('customizing defaults with multiple values does not result in invalid css', async () => {
1082 | let config = {
1083 | plugins: [typographyPlugin()],
1084 | content: [
1085 | {
1086 | raw: html``,
1087 | },
1088 | ],
1089 | theme: {
1090 | typography: {
1091 | DEFAULT: {
1092 | css: {
1093 | textAlign: ['-webkit-match-parent', 'match-parent'],
1094 | },
1095 | },
1096 | },
1097 | },
1098 | }
1099 | return run(config).then((result) => {
1100 | expect(result.css).toMatchFormattedCss(
1101 | css`
1102 | ${defaults}
1103 |
1104 | .prose {
1105 | text-align: -webkit-match-parent;
1106 | text-align: match-parent;
1107 | }
1108 | `
1109 | )
1110 | })
1111 | })
1112 |
1113 | it('should be possible to use nested syntax (&) when extending the config', () => {
1114 | let config = {
1115 | plugins: [typographyPlugin()],
1116 | content: [
1117 | {
1118 | raw: html``,
1119 | },
1120 | ],
1121 | theme: {
1122 | extend: {
1123 | typography: {
1124 | DEFAULT: {
1125 | css: {
1126 | color: '#000',
1127 | a: {
1128 | color: '#888',
1129 | '&:hover': {
1130 | color: '#ff0000',
1131 | },
1132 | },
1133 | },
1134 | },
1135 | },
1136 | },
1137 | },
1138 | }
1139 |
1140 | return run(config).then((result) => {
1141 | expect(result.css).toIncludeCss(css`
1142 | .prose {
1143 | color: #000;
1144 | max-width: 65ch;
1145 | }
1146 | `)
1147 |
1148 | expect(result.css).toIncludeCss(css`
1149 | .prose :where(a):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
1150 | color: #888;
1151 | text-decoration: underline;
1152 | font-weight: 500;
1153 | }
1154 | `)
1155 |
1156 | expect(result.css).toIncludeCss(css`
1157 | .prose :where(a):not(:where([class~='not-prose'], [class~='not-prose'] *)):hover {
1158 | color: #ff0000;
1159 | }
1160 | `)
1161 | })
1162 | })
1163 |
1164 | it('should be possible to specify custom h5 and h6 styles', () => {
1165 | let config = {
1166 | plugins: [typographyPlugin()],
1167 | content: [
1168 | {
1169 | raw: html``,
1170 | },
1171 | ],
1172 | }
1173 |
1174 | return run(config).then((result) => {
1175 | expect(result.css).toIncludeCss(css`
1176 | .prose-h5\:text-sm :is(:where(h5):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
1177 | font-size: 0.875rem;
1178 | line-height: 1.25rem;
1179 | }
1180 | .prose-h6\:text-xl :is(:where(h6):not(:where([class~='not-prose'], [class~='not-prose'] *))) {
1181 | font-size: 1.25rem;
1182 | line-height: 1.75rem;
1183 | }
1184 | `)
1185 | })
1186 | })
1187 |
1188 | it('should not break with multiple selectors with pseudo elements using variants', () => {
1189 | let config = {
1190 | darkMode: 'class',
1191 | plugins: [typographyPlugin()],
1192 | content: [
1193 | {
1194 | raw: html``,
1195 | },
1196 | ],
1197 | theme: {
1198 | typography: {
1199 | DEFAULT: {
1200 | css: {
1201 | 'ol li::before, ul li::before': {
1202 | color: 'red',
1203 | },
1204 | },
1205 | },
1206 | },
1207 | },
1208 | }
1209 |
1210 | return run(config).then((result) => {
1211 | expect(result.css).toIncludeCss(css`
1212 | .dark
1213 | .dark\:prose
1214 | :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::before {
1215 | color: red;
1216 | }
1217 | `)
1218 | })
1219 | })
1220 |
1221 | it('lifts all common, trailing pseudo elements when the same across all selectors', () => {
1222 | let config = {
1223 | darkMode: 'class',
1224 | plugins: [typographyPlugin()],
1225 | content: [
1226 | {
1227 | raw: html``,
1228 | },
1229 | ],
1230 | theme: {
1231 | typography: {
1232 | DEFAULT: {
1233 | css: {
1234 | 'ol li::marker::before, ul li::marker::before': {
1235 | color: 'red',
1236 | },
1237 | },
1238 | },
1239 | },
1240 | },
1241 | }
1242 |
1243 | return run(config).then((result) => {
1244 | expect(result.css).toIncludeCss(css`
1245 | .prose
1246 | :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose']
1247 | *))::marker::before {
1248 | color: red;
1249 | }
1250 | `)
1251 |
1252 | // TODO: The output here is a bug in tailwindcss variant selector rewriting
1253 | // IT should be ::marker::before
1254 | expect(result.css).toIncludeCss(css`
1255 | .dark
1256 | .dark\:prose
1257 | :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose']
1258 | *))::before::marker {
1259 | color: red;
1260 | }
1261 | `)
1262 | })
1263 | })
1264 |
1265 | it('does not modify selectors with differing pseudo elements', () => {
1266 | let config = {
1267 | darkMode: 'class',
1268 | plugins: [typographyPlugin()],
1269 | content: [
1270 | {
1271 | raw: html``,
1272 | },
1273 | ],
1274 | theme: {
1275 | typography: {
1276 | DEFAULT: {
1277 | css: {
1278 | 'ol li::before, ul li::after': {
1279 | color: 'red',
1280 | },
1281 | },
1282 | },
1283 | },
1284 | },
1285 | }
1286 |
1287 | return run(config).then((result) => {
1288 | expect(result.css).toIncludeCss(css`
1289 | .prose
1290 | :where(ol li::before, ul li::after):not(:where([class~='not-prose'], [class~='not-prose']
1291 | *)) {
1292 | color: red;
1293 | }
1294 | `)
1295 |
1296 | // TODO: The output here is a bug in tailwindcss variant selector rewriting
1297 | expect(result.css).toIncludeCss(css`
1298 | .dark
1299 | .dark\:prose
1300 | :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose'] *))::before,
1301 | ::after {
1302 | color: red;
1303 | }
1304 | `)
1305 | })
1306 | })
1307 |
1308 | it('lifts only the common, trailing pseudo elements from selectors', () => {
1309 | let config = {
1310 | darkMode: 'class',
1311 | plugins: [typographyPlugin()],
1312 | content: [
1313 | {
1314 | raw: html``,
1315 | },
1316 | ],
1317 | theme: {
1318 | typography: {
1319 | DEFAULT: {
1320 | css: {
1321 | 'ol li::scroll-thumb::before, ul li::scroll-track::before': {
1322 | color: 'red',
1323 | },
1324 | },
1325 | },
1326 | },
1327 | },
1328 | }
1329 |
1330 | return run(config).then((result) => {
1331 | expect(result.css).toIncludeCss(css`
1332 | .prose
1333 | :where(ol li::scroll-thumb, ul
1334 | li::scroll-track):not(:where([class~='not-prose'], [class~='not-prose'] *))::before {
1335 | color: red;
1336 | }
1337 | `)
1338 |
1339 | // TODO: The output here is a bug in tailwindcss variant selector rewriting
1340 | expect(result.css).toIncludeCss(css`
1341 | .dark
1342 | .dark\:prose
1343 | :where(ol li, ul li):not(:where([class~='not-prose'], [class~='not-prose']
1344 | *))::scroll-thumb,
1345 | ::scroll-track,
1346 | ::before {
1347 | color: red;
1348 | }
1349 | `)
1350 | })
1351 | })
1352 |
1353 | it('ignores common non-trailing pseudo-elements in selectors', () => {
1354 | let config = {
1355 | darkMode: 'class',
1356 | plugins: [typographyPlugin()],
1357 | content: [
1358 | {
1359 | raw: html``,
1360 | },
1361 | ],
1362 | theme: {
1363 | typography: {
1364 | DEFAULT: {
1365 | css: {
1366 | 'ol li::before::scroll-thumb, ul li::before::scroll-track': {
1367 | color: 'red',
1368 | },
1369 | },
1370 | },
1371 | },
1372 | },
1373 | }
1374 |
1375 | return run(config).then((result) => {
1376 | expect(result.css).toIncludeCss(css`
1377 | .prose
1378 | :where(ol li::before::scroll-thumb, ul
1379 | li::before::scroll-track):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
1380 | color: red;
1381 | }
1382 | `)
1383 |
1384 | // TODO: The output here is a bug in tailwindcss variant selector rewriting
1385 | expect(result.css).toIncludeCss(css`
1386 | .dark
1387 | .dark\:prose
1388 | :where(ol li::scroll-thumb, ul
1389 | li::scroll-track):not(:where([class~='not-prose'], [class~='not-prose'] *))::before,
1390 | ::before {
1391 | color: red;
1392 | }
1393 | `)
1394 | })
1395 | })
1396 |
1397 | test('lead styles are inserted after paragraph styles', async () => {
1398 | let config = {
1399 | content: [{ raw: html`` }],
1400 | }
1401 |
1402 | return run(config).then((result) => {
1403 | expect(result.css).toIncludeCss(
1404 | css`
1405 | .prose {
1406 | color: var(--tw-prose-body);
1407 | max-width: 65ch;
1408 | }
1409 | .prose :where(p):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
1410 | margin-top: 1.25em;
1411 | margin-bottom: 1.25em;
1412 | }
1413 | .prose :where([class~='lead']):not(:where([class~='not-prose'], [class~='not-prose'] *)) {
1414 | color: var(--tw-prose-lead);
1415 | font-size: 1.25em;
1416 | line-height: 1.6;
1417 | margin-top: 1.2em;
1418 | margin-bottom: 1.2em;
1419 | }
1420 | `
1421 | )
1422 | })
1423 | })
1424 |
--------------------------------------------------------------------------------
/src/styles.js:
--------------------------------------------------------------------------------
1 | const colors = require('tailwindcss/colors')
2 |
3 | const round = (num) =>
4 | num
5 | .toFixed(7)
6 | .replace(/(\.[0-9]+?)0+$/, '$1')
7 | .replace(/\.0$/, '')
8 | const rem = (px) => `${round(px / 16)}rem`
9 | const em = (px, base) => `${round(px / base)}em`
10 | const hexToRgb = (hex) => {
11 | hex = hex.replace('#', '')
12 | hex = hex.length === 3 ? hex.replace(/./g, '$&$&') : hex
13 | const r = parseInt(hex.substring(0, 2), 16)
14 | const g = parseInt(hex.substring(2, 4), 16)
15 | const b = parseInt(hex.substring(4, 6), 16)
16 | return `${r} ${g} ${b}`
17 | }
18 |
19 | let defaultModifiers = {
20 | sm: {
21 | css: [
22 | {
23 | fontSize: rem(14),
24 | lineHeight: round(24 / 14),
25 | p: {
26 | marginTop: em(16, 14),
27 | marginBottom: em(16, 14),
28 | },
29 | '[class~="lead"]': {
30 | fontSize: em(18, 14),
31 | lineHeight: round(28 / 18),
32 | marginTop: em(16, 18),
33 | marginBottom: em(16, 18),
34 | },
35 | blockquote: {
36 | marginTop: em(24, 18),
37 | marginBottom: em(24, 18),
38 | paddingInlineStart: em(20, 18),
39 | },
40 | h1: {
41 | fontSize: em(30, 14),
42 | marginTop: '0',
43 | marginBottom: em(24, 30),
44 | lineHeight: round(36 / 30),
45 | },
46 | h2: {
47 | fontSize: em(20, 14),
48 | marginTop: em(32, 20),
49 | marginBottom: em(16, 20),
50 | lineHeight: round(28 / 20),
51 | },
52 | h3: {
53 | fontSize: em(18, 14),
54 | marginTop: em(28, 18),
55 | marginBottom: em(8, 18),
56 | lineHeight: round(28 / 18),
57 | },
58 | h4: {
59 | marginTop: em(20, 14),
60 | marginBottom: em(8, 14),
61 | lineHeight: round(20 / 14),
62 | },
63 | img: {
64 | marginTop: em(24, 14),
65 | marginBottom: em(24, 14),
66 | },
67 | picture: {
68 | marginTop: em(24, 14),
69 | marginBottom: em(24, 14),
70 | },
71 | 'picture > img': {
72 | marginTop: '0',
73 | marginBottom: '0',
74 | },
75 | video: {
76 | marginTop: em(24, 14),
77 | marginBottom: em(24, 14),
78 | },
79 | kbd: {
80 | fontSize: em(12, 14),
81 | borderRadius: rem(5),
82 | paddingTop: em(2, 14),
83 | paddingInlineEnd: em(5, 14),
84 | paddingBottom: em(2, 14),
85 | paddingInlineStart: em(5, 14),
86 | },
87 | code: {
88 | fontSize: em(12, 14),
89 | },
90 | 'h2 code': {
91 | fontSize: em(18, 20),
92 | },
93 | 'h3 code': {
94 | fontSize: em(16, 18),
95 | },
96 | pre: {
97 | fontSize: em(12, 14),
98 | lineHeight: round(20 / 12),
99 | marginTop: em(20, 12),
100 | marginBottom: em(20, 12),
101 | borderRadius: rem(4),
102 | paddingTop: em(8, 12),
103 | paddingInlineEnd: em(12, 12),
104 | paddingBottom: em(8, 12),
105 | paddingInlineStart: em(12, 12),
106 | },
107 | ol: {
108 | marginTop: em(16, 14),
109 | marginBottom: em(16, 14),
110 | paddingInlineStart: em(22, 14),
111 | },
112 | ul: {
113 | marginTop: em(16, 14),
114 | marginBottom: em(16, 14),
115 | paddingInlineStart: em(22, 14),
116 | },
117 | li: {
118 | marginTop: em(4, 14),
119 | marginBottom: em(4, 14),
120 | },
121 | 'ol > li': {
122 | paddingInlineStart: em(6, 14),
123 | },
124 | 'ul > li': {
125 | paddingInlineStart: em(6, 14),
126 | },
127 | '> ul > li p': {
128 | marginTop: em(8, 14),
129 | marginBottom: em(8, 14),
130 | },
131 | '> ul > li > p:first-child': {
132 | marginTop: em(16, 14),
133 | },
134 | '> ul > li > p:last-child': {
135 | marginBottom: em(16, 14),
136 | },
137 | '> ol > li > p:first-child': {
138 | marginTop: em(16, 14),
139 | },
140 | '> ol > li > p:last-child': {
141 | marginBottom: em(16, 14),
142 | },
143 | 'ul ul, ul ol, ol ul, ol ol': {
144 | marginTop: em(8, 14),
145 | marginBottom: em(8, 14),
146 | },
147 | dl: {
148 | marginTop: em(16, 14),
149 | marginBottom: em(16, 14),
150 | },
151 | dt: {
152 | marginTop: em(16, 14),
153 | },
154 | dd: {
155 | marginTop: em(4, 14),
156 | paddingInlineStart: em(22, 14),
157 | },
158 | hr: {
159 | marginTop: em(40, 14),
160 | marginBottom: em(40, 14),
161 | },
162 | 'hr + *': {
163 | marginTop: '0',
164 | },
165 | 'h2 + *': {
166 | marginTop: '0',
167 | },
168 | 'h3 + *': {
169 | marginTop: '0',
170 | },
171 | 'h4 + *': {
172 | marginTop: '0',
173 | },
174 | table: {
175 | fontSize: em(12, 14),
176 | lineHeight: round(18 / 12),
177 | },
178 | 'thead th': {
179 | paddingInlineEnd: em(12, 12),
180 | paddingBottom: em(8, 12),
181 | paddingInlineStart: em(12, 12),
182 | },
183 | 'thead th:first-child': {
184 | paddingInlineStart: '0',
185 | },
186 | 'thead th:last-child': {
187 | paddingInlineEnd: '0',
188 | },
189 | 'tbody td, tfoot td': {
190 | paddingTop: em(8, 12),
191 | paddingInlineEnd: em(12, 12),
192 | paddingBottom: em(8, 12),
193 | paddingInlineStart: em(12, 12),
194 | },
195 | 'tbody td:first-child, tfoot td:first-child': {
196 | paddingInlineStart: '0',
197 | },
198 | 'tbody td:last-child, tfoot td:last-child': {
199 | paddingInlineEnd: '0',
200 | },
201 | figure: {
202 | marginTop: em(24, 14),
203 | marginBottom: em(24, 14),
204 | },
205 | 'figure > *': {
206 | marginTop: '0',
207 | marginBottom: '0',
208 | },
209 | figcaption: {
210 | fontSize: em(12, 14),
211 | lineHeight: round(16 / 12),
212 | marginTop: em(8, 12),
213 | },
214 | },
215 | {
216 | '> :first-child': {
217 | marginTop: '0',
218 | },
219 | '> :last-child': {
220 | marginBottom: '0',
221 | },
222 | },
223 | ],
224 | },
225 | base: {
226 | css: [
227 | {
228 | fontSize: rem(16),
229 | lineHeight: round(28 / 16),
230 | p: {
231 | marginTop: em(20, 16),
232 | marginBottom: em(20, 16),
233 | },
234 | '[class~="lead"]': {
235 | fontSize: em(20, 16),
236 | lineHeight: round(32 / 20),
237 | marginTop: em(24, 20),
238 | marginBottom: em(24, 20),
239 | },
240 | blockquote: {
241 | marginTop: em(32, 20),
242 | marginBottom: em(32, 20),
243 | paddingInlineStart: em(20, 20),
244 | },
245 | h1: {
246 | fontSize: em(36, 16),
247 | marginTop: '0',
248 | marginBottom: em(32, 36),
249 | lineHeight: round(40 / 36),
250 | },
251 | h2: {
252 | fontSize: em(24, 16),
253 | marginTop: em(48, 24),
254 | marginBottom: em(24, 24),
255 | lineHeight: round(32 / 24),
256 | },
257 | h3: {
258 | fontSize: em(20, 16),
259 | marginTop: em(32, 20),
260 | marginBottom: em(12, 20),
261 | lineHeight: round(32 / 20),
262 | },
263 | h4: {
264 | marginTop: em(24, 16),
265 | marginBottom: em(8, 16),
266 | lineHeight: round(24 / 16),
267 | },
268 | img: {
269 | marginTop: em(32, 16),
270 | marginBottom: em(32, 16),
271 | },
272 | picture: {
273 | marginTop: em(32, 16),
274 | marginBottom: em(32, 16),
275 | },
276 | 'picture > img': {
277 | marginTop: '0',
278 | marginBottom: '0',
279 | },
280 | video: {
281 | marginTop: em(32, 16),
282 | marginBottom: em(32, 16),
283 | },
284 | kbd: {
285 | fontSize: em(14, 16),
286 | borderRadius: rem(5),
287 | paddingTop: em(3, 16),
288 | paddingInlineEnd: em(6, 16),
289 | paddingBottom: em(3, 16),
290 | paddingInlineStart: em(6, 16),
291 | },
292 | code: {
293 | fontSize: em(14, 16),
294 | },
295 | 'h2 code': {
296 | fontSize: em(21, 24),
297 | },
298 | 'h3 code': {
299 | fontSize: em(18, 20),
300 | },
301 | pre: {
302 | fontSize: em(14, 16),
303 | lineHeight: round(24 / 14),
304 | marginTop: em(24, 14),
305 | marginBottom: em(24, 14),
306 | borderRadius: rem(6),
307 | paddingTop: em(12, 14),
308 | paddingInlineEnd: em(16, 14),
309 | paddingBottom: em(12, 14),
310 | paddingInlineStart: em(16, 14),
311 | },
312 | ol: {
313 | marginTop: em(20, 16),
314 | marginBottom: em(20, 16),
315 | paddingInlineStart: em(26, 16),
316 | },
317 | ul: {
318 | marginTop: em(20, 16),
319 | marginBottom: em(20, 16),
320 | paddingInlineStart: em(26, 16),
321 | },
322 | li: {
323 | marginTop: em(8, 16),
324 | marginBottom: em(8, 16),
325 | },
326 | 'ol > li': {
327 | paddingInlineStart: em(6, 16),
328 | },
329 | 'ul > li': {
330 | paddingInlineStart: em(6, 16),
331 | },
332 | '> ul > li p': {
333 | marginTop: em(12, 16),
334 | marginBottom: em(12, 16),
335 | },
336 | '> ul > li > p:first-child': {
337 | marginTop: em(20, 16),
338 | },
339 | '> ul > li > p:last-child': {
340 | marginBottom: em(20, 16),
341 | },
342 | '> ol > li > p:first-child': {
343 | marginTop: em(20, 16),
344 | },
345 | '> ol > li > p:last-child': {
346 | marginBottom: em(20, 16),
347 | },
348 | 'ul ul, ul ol, ol ul, ol ol': {
349 | marginTop: em(12, 16),
350 | marginBottom: em(12, 16),
351 | },
352 | dl: {
353 | marginTop: em(20, 16),
354 | marginBottom: em(20, 16),
355 | },
356 | dt: {
357 | marginTop: em(20, 16),
358 | },
359 | dd: {
360 | marginTop: em(8, 16),
361 | paddingInlineStart: em(26, 16),
362 | },
363 | hr: {
364 | marginTop: em(48, 16),
365 | marginBottom: em(48, 16),
366 | },
367 | 'hr + *': {
368 | marginTop: '0',
369 | },
370 | 'h2 + *': {
371 | marginTop: '0',
372 | },
373 | 'h3 + *': {
374 | marginTop: '0',
375 | },
376 | 'h4 + *': {
377 | marginTop: '0',
378 | },
379 | table: {
380 | fontSize: em(14, 16),
381 | lineHeight: round(24 / 14),
382 | },
383 | 'thead th': {
384 | paddingInlineEnd: em(8, 14),
385 | paddingBottom: em(8, 14),
386 | paddingInlineStart: em(8, 14),
387 | },
388 | 'thead th:first-child': {
389 | paddingInlineStart: '0',
390 | },
391 | 'thead th:last-child': {
392 | paddingInlineEnd: '0',
393 | },
394 | 'tbody td, tfoot td': {
395 | paddingTop: em(8, 14),
396 | paddingInlineEnd: em(8, 14),
397 | paddingBottom: em(8, 14),
398 | paddingInlineStart: em(8, 14),
399 | },
400 | 'tbody td:first-child, tfoot td:first-child': {
401 | paddingInlineStart: '0',
402 | },
403 | 'tbody td:last-child, tfoot td:last-child': {
404 | paddingInlineEnd: '0',
405 | },
406 | figure: {
407 | marginTop: em(32, 16),
408 | marginBottom: em(32, 16),
409 | },
410 | 'figure > *': {
411 | marginTop: '0',
412 | marginBottom: '0',
413 | },
414 | figcaption: {
415 | fontSize: em(14, 16),
416 | lineHeight: round(20 / 14),
417 | marginTop: em(12, 14),
418 | },
419 | },
420 | {
421 | '> :first-child': {
422 | marginTop: '0',
423 | },
424 | '> :last-child': {
425 | marginBottom: '0',
426 | },
427 | },
428 | ],
429 | },
430 | lg: {
431 | css: [
432 | {
433 | fontSize: rem(18),
434 | lineHeight: round(32 / 18),
435 | p: {
436 | marginTop: em(24, 18),
437 | marginBottom: em(24, 18),
438 | },
439 | '[class~="lead"]': {
440 | fontSize: em(22, 18),
441 | lineHeight: round(32 / 22),
442 | marginTop: em(24, 22),
443 | marginBottom: em(24, 22),
444 | },
445 | blockquote: {
446 | marginTop: em(40, 24),
447 | marginBottom: em(40, 24),
448 | paddingInlineStart: em(24, 24),
449 | },
450 | h1: {
451 | fontSize: em(48, 18),
452 | marginTop: '0',
453 | marginBottom: em(40, 48),
454 | lineHeight: round(48 / 48),
455 | },
456 | h2: {
457 | fontSize: em(30, 18),
458 | marginTop: em(56, 30),
459 | marginBottom: em(32, 30),
460 | lineHeight: round(40 / 30),
461 | },
462 | h3: {
463 | fontSize: em(24, 18),
464 | marginTop: em(40, 24),
465 | marginBottom: em(16, 24),
466 | lineHeight: round(36 / 24),
467 | },
468 | h4: {
469 | marginTop: em(32, 18),
470 | marginBottom: em(8, 18),
471 | lineHeight: round(28 / 18),
472 | },
473 | img: {
474 | marginTop: em(32, 18),
475 | marginBottom: em(32, 18),
476 | },
477 | picture: {
478 | marginTop: em(32, 18),
479 | marginBottom: em(32, 18),
480 | },
481 | 'picture > img': {
482 | marginTop: '0',
483 | marginBottom: '0',
484 | },
485 | video: {
486 | marginTop: em(32, 18),
487 | marginBottom: em(32, 18),
488 | },
489 | kbd: {
490 | fontSize: em(16, 18),
491 | borderRadius: rem(5),
492 | paddingTop: em(4, 18),
493 | paddingInlineEnd: em(8, 18),
494 | paddingBottom: em(4, 18),
495 | paddingInlineStart: em(8, 18),
496 | },
497 | code: {
498 | fontSize: em(16, 18),
499 | },
500 | 'h2 code': {
501 | fontSize: em(26, 30),
502 | },
503 | 'h3 code': {
504 | fontSize: em(21, 24),
505 | },
506 | pre: {
507 | fontSize: em(16, 18),
508 | lineHeight: round(28 / 16),
509 | marginTop: em(32, 16),
510 | marginBottom: em(32, 16),
511 | borderRadius: rem(6),
512 | paddingTop: em(16, 16),
513 | paddingInlineEnd: em(24, 16),
514 | paddingBottom: em(16, 16),
515 | paddingInlineStart: em(24, 16),
516 | },
517 | ol: {
518 | marginTop: em(24, 18),
519 | marginBottom: em(24, 18),
520 | paddingInlineStart: em(28, 18),
521 | },
522 | ul: {
523 | marginTop: em(24, 18),
524 | marginBottom: em(24, 18),
525 | paddingInlineStart: em(28, 18),
526 | },
527 | li: {
528 | marginTop: em(12, 18),
529 | marginBottom: em(12, 18),
530 | },
531 | 'ol > li': {
532 | paddingInlineStart: em(8, 18),
533 | },
534 | 'ul > li': {
535 | paddingInlineStart: em(8, 18),
536 | },
537 | '> ul > li p': {
538 | marginTop: em(16, 18),
539 | marginBottom: em(16, 18),
540 | },
541 | '> ul > li > p:first-child': {
542 | marginTop: em(24, 18),
543 | },
544 | '> ul > li > p:last-child': {
545 | marginBottom: em(24, 18),
546 | },
547 | '> ol > li > p:first-child': {
548 | marginTop: em(24, 18),
549 | },
550 | '> ol > li > p:last-child': {
551 | marginBottom: em(24, 18),
552 | },
553 | 'ul ul, ul ol, ol ul, ol ol': {
554 | marginTop: em(16, 18),
555 | marginBottom: em(16, 18),
556 | },
557 | dl: {
558 | marginTop: em(24, 18),
559 | marginBottom: em(24, 18),
560 | },
561 | dt: {
562 | marginTop: em(24, 18),
563 | },
564 | dd: {
565 | marginTop: em(12, 18),
566 | paddingInlineStart: em(28, 18),
567 | },
568 | hr: {
569 | marginTop: em(56, 18),
570 | marginBottom: em(56, 18),
571 | },
572 | 'hr + *': {
573 | marginTop: '0',
574 | },
575 | 'h2 + *': {
576 | marginTop: '0',
577 | },
578 | 'h3 + *': {
579 | marginTop: '0',
580 | },
581 | 'h4 + *': {
582 | marginTop: '0',
583 | },
584 | table: {
585 | fontSize: em(16, 18),
586 | lineHeight: round(24 / 16),
587 | },
588 | 'thead th': {
589 | paddingInlineEnd: em(12, 16),
590 | paddingBottom: em(12, 16),
591 | paddingInlineStart: em(12, 16),
592 | },
593 | 'thead th:first-child': {
594 | paddingInlineStart: '0',
595 | },
596 | 'thead th:last-child': {
597 | paddingInlineEnd: '0',
598 | },
599 | 'tbody td, tfoot td': {
600 | paddingTop: em(12, 16),
601 | paddingInlineEnd: em(12, 16),
602 | paddingBottom: em(12, 16),
603 | paddingInlineStart: em(12, 16),
604 | },
605 | 'tbody td:first-child, tfoot td:first-child': {
606 | paddingInlineStart: '0',
607 | },
608 | 'tbody td:last-child, tfoot td:last-child': {
609 | paddingInlineEnd: '0',
610 | },
611 | figure: {
612 | marginTop: em(32, 18),
613 | marginBottom: em(32, 18),
614 | },
615 | 'figure > *': {
616 | marginTop: '0',
617 | marginBottom: '0',
618 | },
619 | figcaption: {
620 | fontSize: em(16, 18),
621 | lineHeight: round(24 / 16),
622 | marginTop: em(16, 16),
623 | },
624 | },
625 | {
626 | '> :first-child': {
627 | marginTop: '0',
628 | },
629 | '> :last-child': {
630 | marginBottom: '0',
631 | },
632 | },
633 | ],
634 | },
635 | xl: {
636 | css: [
637 | {
638 | fontSize: rem(20),
639 | lineHeight: round(36 / 20),
640 | p: {
641 | marginTop: em(24, 20),
642 | marginBottom: em(24, 20),
643 | },
644 | '[class~="lead"]': {
645 | fontSize: em(24, 20),
646 | lineHeight: round(36 / 24),
647 | marginTop: em(24, 24),
648 | marginBottom: em(24, 24),
649 | },
650 | blockquote: {
651 | marginTop: em(48, 30),
652 | marginBottom: em(48, 30),
653 | paddingInlineStart: em(32, 30),
654 | },
655 | h1: {
656 | fontSize: em(56, 20),
657 | marginTop: '0',
658 | marginBottom: em(48, 56),
659 | lineHeight: round(56 / 56),
660 | },
661 | h2: {
662 | fontSize: em(36, 20),
663 | marginTop: em(56, 36),
664 | marginBottom: em(32, 36),
665 | lineHeight: round(40 / 36),
666 | },
667 | h3: {
668 | fontSize: em(30, 20),
669 | marginTop: em(48, 30),
670 | marginBottom: em(20, 30),
671 | lineHeight: round(40 / 30),
672 | },
673 | h4: {
674 | marginTop: em(36, 20),
675 | marginBottom: em(12, 20),
676 | lineHeight: round(32 / 20),
677 | },
678 | img: {
679 | marginTop: em(40, 20),
680 | marginBottom: em(40, 20),
681 | },
682 | picture: {
683 | marginTop: em(40, 20),
684 | marginBottom: em(40, 20),
685 | },
686 | 'picture > img': {
687 | marginTop: '0',
688 | marginBottom: '0',
689 | },
690 | video: {
691 | marginTop: em(40, 20),
692 | marginBottom: em(40, 20),
693 | },
694 | kbd: {
695 | fontSize: em(18, 20),
696 | borderRadius: rem(5),
697 | paddingTop: em(5, 20),
698 | paddingInlineEnd: em(8, 20),
699 | paddingBottom: em(5, 20),
700 | paddingInlineStart: em(8, 20),
701 | },
702 | code: {
703 | fontSize: em(18, 20),
704 | },
705 | 'h2 code': {
706 | fontSize: em(31, 36),
707 | },
708 | 'h3 code': {
709 | fontSize: em(27, 30),
710 | },
711 | pre: {
712 | fontSize: em(18, 20),
713 | lineHeight: round(32 / 18),
714 | marginTop: em(36, 18),
715 | marginBottom: em(36, 18),
716 | borderRadius: rem(8),
717 | paddingTop: em(20, 18),
718 | paddingInlineEnd: em(24, 18),
719 | paddingBottom: em(20, 18),
720 | paddingInlineStart: em(24, 18),
721 | },
722 | ol: {
723 | marginTop: em(24, 20),
724 | marginBottom: em(24, 20),
725 | paddingInlineStart: em(32, 20),
726 | },
727 | ul: {
728 | marginTop: em(24, 20),
729 | marginBottom: em(24, 20),
730 | paddingInlineStart: em(32, 20),
731 | },
732 | li: {
733 | marginTop: em(12, 20),
734 | marginBottom: em(12, 20),
735 | },
736 | 'ol > li': {
737 | paddingInlineStart: em(8, 20),
738 | },
739 | 'ul > li': {
740 | paddingInlineStart: em(8, 20),
741 | },
742 | '> ul > li p': {
743 | marginTop: em(16, 20),
744 | marginBottom: em(16, 20),
745 | },
746 | '> ul > li > p:first-child': {
747 | marginTop: em(24, 20),
748 | },
749 | '> ul > li > p:last-child': {
750 | marginBottom: em(24, 20),
751 | },
752 | '> ol > li > p:first-child': {
753 | marginTop: em(24, 20),
754 | },
755 | '> ol > li > p:last-child': {
756 | marginBottom: em(24, 20),
757 | },
758 | 'ul ul, ul ol, ol ul, ol ol': {
759 | marginTop: em(16, 20),
760 | marginBottom: em(16, 20),
761 | },
762 | dl: {
763 | marginTop: em(24, 20),
764 | marginBottom: em(24, 20),
765 | },
766 | dt: {
767 | marginTop: em(24, 20),
768 | },
769 | dd: {
770 | marginTop: em(12, 20),
771 | paddingInlineStart: em(32, 20),
772 | },
773 | hr: {
774 | marginTop: em(56, 20),
775 | marginBottom: em(56, 20),
776 | },
777 | 'hr + *': {
778 | marginTop: '0',
779 | },
780 | 'h2 + *': {
781 | marginTop: '0',
782 | },
783 | 'h3 + *': {
784 | marginTop: '0',
785 | },
786 | 'h4 + *': {
787 | marginTop: '0',
788 | },
789 | table: {
790 | fontSize: em(18, 20),
791 | lineHeight: round(28 / 18),
792 | },
793 | 'thead th': {
794 | paddingInlineEnd: em(12, 18),
795 | paddingBottom: em(16, 18),
796 | paddingInlineStart: em(12, 18),
797 | },
798 | 'thead th:first-child': {
799 | paddingInlineStart: '0',
800 | },
801 | 'thead th:last-child': {
802 | paddingInlineEnd: '0',
803 | },
804 | 'tbody td, tfoot td': {
805 | paddingTop: em(16, 18),
806 | paddingInlineEnd: em(12, 18),
807 | paddingBottom: em(16, 18),
808 | paddingInlineStart: em(12, 18),
809 | },
810 | 'tbody td:first-child, tfoot td:first-child': {
811 | paddingInlineStart: '0',
812 | },
813 | 'tbody td:last-child, tfoot td:last-child': {
814 | paddingInlineEnd: '0',
815 | },
816 | figure: {
817 | marginTop: em(40, 20),
818 | marginBottom: em(40, 20),
819 | },
820 | 'figure > *': {
821 | marginTop: '0',
822 | marginBottom: '0',
823 | },
824 | figcaption: {
825 | fontSize: em(18, 20),
826 | lineHeight: round(28 / 18),
827 | marginTop: em(18, 18),
828 | },
829 | },
830 | {
831 | '> :first-child': {
832 | marginTop: '0',
833 | },
834 | '> :last-child': {
835 | marginBottom: '0',
836 | },
837 | },
838 | ],
839 | },
840 | '2xl': {
841 | css: [
842 | {
843 | fontSize: rem(24),
844 | lineHeight: round(40 / 24),
845 | p: {
846 | marginTop: em(32, 24),
847 | marginBottom: em(32, 24),
848 | },
849 | '[class~="lead"]': {
850 | fontSize: em(30, 24),
851 | lineHeight: round(44 / 30),
852 | marginTop: em(32, 30),
853 | marginBottom: em(32, 30),
854 | },
855 | blockquote: {
856 | marginTop: em(64, 36),
857 | marginBottom: em(64, 36),
858 | paddingInlineStart: em(40, 36),
859 | },
860 | h1: {
861 | fontSize: em(64, 24),
862 | marginTop: '0',
863 | marginBottom: em(56, 64),
864 | lineHeight: round(64 / 64),
865 | },
866 | h2: {
867 | fontSize: em(48, 24),
868 | marginTop: em(72, 48),
869 | marginBottom: em(40, 48),
870 | lineHeight: round(52 / 48),
871 | },
872 | h3: {
873 | fontSize: em(36, 24),
874 | marginTop: em(56, 36),
875 | marginBottom: em(24, 36),
876 | lineHeight: round(44 / 36),
877 | },
878 | h4: {
879 | marginTop: em(40, 24),
880 | marginBottom: em(16, 24),
881 | lineHeight: round(36 / 24),
882 | },
883 | img: {
884 | marginTop: em(48, 24),
885 | marginBottom: em(48, 24),
886 | },
887 | picture: {
888 | marginTop: em(48, 24),
889 | marginBottom: em(48, 24),
890 | },
891 | 'picture > img': {
892 | marginTop: '0',
893 | marginBottom: '0',
894 | },
895 | video: {
896 | marginTop: em(48, 24),
897 | marginBottom: em(48, 24),
898 | },
899 | kbd: {
900 | fontSize: em(20, 24),
901 | borderRadius: rem(6),
902 | paddingTop: em(6, 24),
903 | paddingInlineEnd: em(8, 24),
904 | paddingBottom: em(6, 24),
905 | paddingInlineStart: em(8, 24),
906 | },
907 | code: {
908 | fontSize: em(20, 24),
909 | },
910 | 'h2 code': {
911 | fontSize: em(42, 48),
912 | },
913 | 'h3 code': {
914 | fontSize: em(32, 36),
915 | },
916 | pre: {
917 | fontSize: em(20, 24),
918 | lineHeight: round(36 / 20),
919 | marginTop: em(40, 20),
920 | marginBottom: em(40, 20),
921 | borderRadius: rem(8),
922 | paddingTop: em(24, 20),
923 | paddingInlineEnd: em(32, 20),
924 | paddingBottom: em(24, 20),
925 | paddingInlineStart: em(32, 20),
926 | },
927 | ol: {
928 | marginTop: em(32, 24),
929 | marginBottom: em(32, 24),
930 | paddingInlineStart: em(38, 24),
931 | },
932 | ul: {
933 | marginTop: em(32, 24),
934 | marginBottom: em(32, 24),
935 | paddingInlineStart: em(38, 24),
936 | },
937 | li: {
938 | marginTop: em(12, 24),
939 | marginBottom: em(12, 24),
940 | },
941 | 'ol > li': {
942 | paddingInlineStart: em(10, 24),
943 | },
944 | 'ul > li': {
945 | paddingInlineStart: em(10, 24),
946 | },
947 | '> ul > li p': {
948 | marginTop: em(20, 24),
949 | marginBottom: em(20, 24),
950 | },
951 | '> ul > li > p:first-child': {
952 | marginTop: em(32, 24),
953 | },
954 | '> ul > li > p:last-child': {
955 | marginBottom: em(32, 24),
956 | },
957 | '> ol > li > p:first-child': {
958 | marginTop: em(32, 24),
959 | },
960 | '> ol > li > p:last-child': {
961 | marginBottom: em(32, 24),
962 | },
963 | 'ul ul, ul ol, ol ul, ol ol': {
964 | marginTop: em(16, 24),
965 | marginBottom: em(16, 24),
966 | },
967 | dl: {
968 | marginTop: em(32, 24),
969 | marginBottom: em(32, 24),
970 | },
971 | dt: {
972 | marginTop: em(32, 24),
973 | },
974 | dd: {
975 | marginTop: em(12, 24),
976 | paddingInlineStart: em(38, 24),
977 | },
978 | hr: {
979 | marginTop: em(72, 24),
980 | marginBottom: em(72, 24),
981 | },
982 | 'hr + *': {
983 | marginTop: '0',
984 | },
985 | 'h2 + *': {
986 | marginTop: '0',
987 | },
988 | 'h3 + *': {
989 | marginTop: '0',
990 | },
991 | 'h4 + *': {
992 | marginTop: '0',
993 | },
994 | table: {
995 | fontSize: em(20, 24),
996 | lineHeight: round(28 / 20),
997 | },
998 | 'thead th': {
999 | paddingInlineEnd: em(12, 20),
1000 | paddingBottom: em(16, 20),
1001 | paddingInlineStart: em(12, 20),
1002 | },
1003 | 'thead th:first-child': {
1004 | paddingInlineStart: '0',
1005 | },
1006 | 'thead th:last-child': {
1007 | paddingInlineEnd: '0',
1008 | },
1009 | 'tbody td, tfoot td': {
1010 | paddingTop: em(16, 20),
1011 | paddingInlineEnd: em(12, 20),
1012 | paddingBottom: em(16, 20),
1013 | paddingInlineStart: em(12, 20),
1014 | },
1015 | 'tbody td:first-child, tfoot td:first-child': {
1016 | paddingInlineStart: '0',
1017 | },
1018 | 'tbody td:last-child, tfoot td:last-child': {
1019 | paddingInlineEnd: '0',
1020 | },
1021 | figure: {
1022 | marginTop: em(48, 24),
1023 | marginBottom: em(48, 24),
1024 | },
1025 | 'figure > *': {
1026 | marginTop: '0',
1027 | marginBottom: '0',
1028 | },
1029 | figcaption: {
1030 | fontSize: em(20, 24),
1031 | lineHeight: round(32 / 20),
1032 | marginTop: em(20, 20),
1033 | },
1034 | },
1035 | {
1036 | '> :first-child': {
1037 | marginTop: '0',
1038 | },
1039 | '> :last-child': {
1040 | marginBottom: '0',
1041 | },
1042 | },
1043 | ],
1044 | },
1045 |
1046 | // Gray color themes
1047 |
1048 | slate: {
1049 | css: {
1050 | '--tw-prose-body': colors.slate[700],
1051 | '--tw-prose-headings': colors.slate[900],
1052 | '--tw-prose-lead': colors.slate[600],
1053 | '--tw-prose-links': colors.slate[900],
1054 | '--tw-prose-bold': colors.slate[900],
1055 | '--tw-prose-counters': colors.slate[500],
1056 | '--tw-prose-bullets': colors.slate[300],
1057 | '--tw-prose-hr': colors.slate[200],
1058 | '--tw-prose-quotes': colors.slate[900],
1059 | '--tw-prose-quote-borders': colors.slate[200],
1060 | '--tw-prose-captions': colors.slate[500],
1061 | '--tw-prose-kbd': colors.slate[900],
1062 | '--tw-prose-kbd-shadows': hexToRgb(colors.slate[900]),
1063 | '--tw-prose-code': colors.slate[900],
1064 | '--tw-prose-pre-code': colors.slate[200],
1065 | '--tw-prose-pre-bg': colors.slate[800],
1066 | '--tw-prose-th-borders': colors.slate[300],
1067 | '--tw-prose-td-borders': colors.slate[200],
1068 | '--tw-prose-invert-body': colors.slate[300],
1069 | '--tw-prose-invert-headings': colors.white,
1070 | '--tw-prose-invert-lead': colors.slate[400],
1071 | '--tw-prose-invert-links': colors.white,
1072 | '--tw-prose-invert-bold': colors.white,
1073 | '--tw-prose-invert-counters': colors.slate[400],
1074 | '--tw-prose-invert-bullets': colors.slate[600],
1075 | '--tw-prose-invert-hr': colors.slate[700],
1076 | '--tw-prose-invert-quotes': colors.slate[100],
1077 | '--tw-prose-invert-quote-borders': colors.slate[700],
1078 | '--tw-prose-invert-captions': colors.slate[400],
1079 | '--tw-prose-invert-kbd': colors.white,
1080 | '--tw-prose-invert-kbd-shadows': hexToRgb(colors.white),
1081 | '--tw-prose-invert-code': colors.white,
1082 | '--tw-prose-invert-pre-code': colors.slate[300],
1083 | '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
1084 | '--tw-prose-invert-th-borders': colors.slate[600],
1085 | '--tw-prose-invert-td-borders': colors.slate[700],
1086 | },
1087 | },
1088 |
1089 | gray: {
1090 | css: {
1091 | '--tw-prose-body': colors.gray[700],
1092 | '--tw-prose-headings': colors.gray[900],
1093 | '--tw-prose-lead': colors.gray[600],
1094 | '--tw-prose-links': colors.gray[900],
1095 | '--tw-prose-bold': colors.gray[900],
1096 | '--tw-prose-counters': colors.gray[500],
1097 | '--tw-prose-bullets': colors.gray[300],
1098 | '--tw-prose-hr': colors.gray[200],
1099 | '--tw-prose-quotes': colors.gray[900],
1100 | '--tw-prose-quote-borders': colors.gray[200],
1101 | '--tw-prose-captions': colors.gray[500],
1102 | '--tw-prose-kbd': colors.gray[900],
1103 | '--tw-prose-kbd-shadows': hexToRgb(colors.gray[900]),
1104 | '--tw-prose-code': colors.gray[900],
1105 | '--tw-prose-pre-code': colors.gray[200],
1106 | '--tw-prose-pre-bg': colors.gray[800],
1107 | '--tw-prose-th-borders': colors.gray[300],
1108 | '--tw-prose-td-borders': colors.gray[200],
1109 | '--tw-prose-invert-body': colors.gray[300],
1110 | '--tw-prose-invert-headings': colors.white,
1111 | '--tw-prose-invert-lead': colors.gray[400],
1112 | '--tw-prose-invert-links': colors.white,
1113 | '--tw-prose-invert-bold': colors.white,
1114 | '--tw-prose-invert-counters': colors.gray[400],
1115 | '--tw-prose-invert-bullets': colors.gray[600],
1116 | '--tw-prose-invert-hr': colors.gray[700],
1117 | '--tw-prose-invert-quotes': colors.gray[100],
1118 | '--tw-prose-invert-quote-borders': colors.gray[700],
1119 | '--tw-prose-invert-captions': colors.gray[400],
1120 | '--tw-prose-invert-kbd': colors.white,
1121 | '--tw-prose-invert-kbd-shadows': hexToRgb(colors.white),
1122 | '--tw-prose-invert-code': colors.white,
1123 | '--tw-prose-invert-pre-code': colors.gray[300],
1124 | '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
1125 | '--tw-prose-invert-th-borders': colors.gray[600],
1126 | '--tw-prose-invert-td-borders': colors.gray[700],
1127 | },
1128 | },
1129 |
1130 | zinc: {
1131 | css: {
1132 | '--tw-prose-body': colors.zinc[700],
1133 | '--tw-prose-headings': colors.zinc[900],
1134 | '--tw-prose-lead': colors.zinc[600],
1135 | '--tw-prose-links': colors.zinc[900],
1136 | '--tw-prose-bold': colors.zinc[900],
1137 | '--tw-prose-counters': colors.zinc[500],
1138 | '--tw-prose-bullets': colors.zinc[300],
1139 | '--tw-prose-hr': colors.zinc[200],
1140 | '--tw-prose-quotes': colors.zinc[900],
1141 | '--tw-prose-quote-borders': colors.zinc[200],
1142 | '--tw-prose-captions': colors.zinc[500],
1143 | '--tw-prose-kbd': colors.zinc[900],
1144 | '--tw-prose-kbd-shadows': hexToRgb(colors.zinc[900]),
1145 | '--tw-prose-code': colors.zinc[900],
1146 | '--tw-prose-pre-code': colors.zinc[200],
1147 | '--tw-prose-pre-bg': colors.zinc[800],
1148 | '--tw-prose-th-borders': colors.zinc[300],
1149 | '--tw-prose-td-borders': colors.zinc[200],
1150 | '--tw-prose-invert-body': colors.zinc[300],
1151 | '--tw-prose-invert-headings': colors.white,
1152 | '--tw-prose-invert-lead': colors.zinc[400],
1153 | '--tw-prose-invert-links': colors.white,
1154 | '--tw-prose-invert-bold': colors.white,
1155 | '--tw-prose-invert-counters': colors.zinc[400],
1156 | '--tw-prose-invert-bullets': colors.zinc[600],
1157 | '--tw-prose-invert-hr': colors.zinc[700],
1158 | '--tw-prose-invert-quotes': colors.zinc[100],
1159 | '--tw-prose-invert-quote-borders': colors.zinc[700],
1160 | '--tw-prose-invert-captions': colors.zinc[400],
1161 | '--tw-prose-invert-kbd': colors.white,
1162 | '--tw-prose-invert-kbd-shadows': hexToRgb(colors.white),
1163 | '--tw-prose-invert-code': colors.white,
1164 | '--tw-prose-invert-pre-code': colors.zinc[300],
1165 | '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
1166 | '--tw-prose-invert-th-borders': colors.zinc[600],
1167 | '--tw-prose-invert-td-borders': colors.zinc[700],
1168 | },
1169 | },
1170 |
1171 | neutral: {
1172 | css: {
1173 | '--tw-prose-body': colors.neutral[700],
1174 | '--tw-prose-headings': colors.neutral[900],
1175 | '--tw-prose-lead': colors.neutral[600],
1176 | '--tw-prose-links': colors.neutral[900],
1177 | '--tw-prose-bold': colors.neutral[900],
1178 | '--tw-prose-counters': colors.neutral[500],
1179 | '--tw-prose-bullets': colors.neutral[300],
1180 | '--tw-prose-hr': colors.neutral[200],
1181 | '--tw-prose-quotes': colors.neutral[900],
1182 | '--tw-prose-quote-borders': colors.neutral[200],
1183 | '--tw-prose-captions': colors.neutral[500],
1184 | '--tw-prose-kbd': colors.neutral[900],
1185 | '--tw-prose-kbd-shadows': hexToRgb(colors.neutral[900]),
1186 | '--tw-prose-code': colors.neutral[900],
1187 | '--tw-prose-pre-code': colors.neutral[200],
1188 | '--tw-prose-pre-bg': colors.neutral[800],
1189 | '--tw-prose-th-borders': colors.neutral[300],
1190 | '--tw-prose-td-borders': colors.neutral[200],
1191 | '--tw-prose-invert-body': colors.neutral[300],
1192 | '--tw-prose-invert-headings': colors.white,
1193 | '--tw-prose-invert-lead': colors.neutral[400],
1194 | '--tw-prose-invert-links': colors.white,
1195 | '--tw-prose-invert-bold': colors.white,
1196 | '--tw-prose-invert-counters': colors.neutral[400],
1197 | '--tw-prose-invert-bullets': colors.neutral[600],
1198 | '--tw-prose-invert-hr': colors.neutral[700],
1199 | '--tw-prose-invert-quotes': colors.neutral[100],
1200 | '--tw-prose-invert-quote-borders': colors.neutral[700],
1201 | '--tw-prose-invert-captions': colors.neutral[400],
1202 | '--tw-prose-invert-kbd': colors.white,
1203 | '--tw-prose-invert-kbd-shadows': hexToRgb(colors.white),
1204 | '--tw-prose-invert-code': colors.white,
1205 | '--tw-prose-invert-pre-code': colors.neutral[300],
1206 | '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
1207 | '--tw-prose-invert-th-borders': colors.neutral[600],
1208 | '--tw-prose-invert-td-borders': colors.neutral[700],
1209 | },
1210 | },
1211 |
1212 | stone: {
1213 | css: {
1214 | '--tw-prose-body': colors.stone[700],
1215 | '--tw-prose-headings': colors.stone[900],
1216 | '--tw-prose-lead': colors.stone[600],
1217 | '--tw-prose-links': colors.stone[900],
1218 | '--tw-prose-bold': colors.stone[900],
1219 | '--tw-prose-counters': colors.stone[500],
1220 | '--tw-prose-bullets': colors.stone[300],
1221 | '--tw-prose-hr': colors.stone[200],
1222 | '--tw-prose-quotes': colors.stone[900],
1223 | '--tw-prose-quote-borders': colors.stone[200],
1224 | '--tw-prose-captions': colors.stone[500],
1225 | '--tw-prose-kbd': colors.stone[900],
1226 | '--tw-prose-kbd-shadows': hexToRgb(colors.stone[900]),
1227 | '--tw-prose-code': colors.stone[900],
1228 | '--tw-prose-pre-code': colors.stone[200],
1229 | '--tw-prose-pre-bg': colors.stone[800],
1230 | '--tw-prose-th-borders': colors.stone[300],
1231 | '--tw-prose-td-borders': colors.stone[200],
1232 | '--tw-prose-invert-body': colors.stone[300],
1233 | '--tw-prose-invert-headings': colors.white,
1234 | '--tw-prose-invert-lead': colors.stone[400],
1235 | '--tw-prose-invert-links': colors.white,
1236 | '--tw-prose-invert-bold': colors.white,
1237 | '--tw-prose-invert-counters': colors.stone[400],
1238 | '--tw-prose-invert-bullets': colors.stone[600],
1239 | '--tw-prose-invert-hr': colors.stone[700],
1240 | '--tw-prose-invert-quotes': colors.stone[100],
1241 | '--tw-prose-invert-quote-borders': colors.stone[700],
1242 | '--tw-prose-invert-captions': colors.stone[400],
1243 | '--tw-prose-invert-kbd': colors.white,
1244 | '--tw-prose-invert-kbd-shadows': hexToRgb(colors.white),
1245 | '--tw-prose-invert-code': colors.white,
1246 | '--tw-prose-invert-pre-code': colors.stone[300],
1247 | '--tw-prose-invert-pre-bg': 'rgb(0 0 0 / 50%)',
1248 | '--tw-prose-invert-th-borders': colors.stone[600],
1249 | '--tw-prose-invert-td-borders': colors.stone[700],
1250 | },
1251 | },
1252 |
1253 | // Link-only themes (for backward compatibility)
1254 |
1255 | red: {
1256 | css: {
1257 | '--tw-prose-links': colors.red[600],
1258 | '--tw-prose-invert-links': colors.red[500],
1259 | },
1260 | },
1261 |
1262 | orange: {
1263 | css: {
1264 | '--tw-prose-links': colors.orange[600],
1265 | '--tw-prose-invert-links': colors.orange[500],
1266 | },
1267 | },
1268 |
1269 | amber: {
1270 | css: {
1271 | '--tw-prose-links': colors.amber[600],
1272 | '--tw-prose-invert-links': colors.amber[500],
1273 | },
1274 | },
1275 |
1276 | yellow: {
1277 | css: {
1278 | '--tw-prose-links': colors.yellow[600],
1279 | '--tw-prose-invert-links': colors.yellow[500],
1280 | },
1281 | },
1282 |
1283 | lime: {
1284 | css: {
1285 | '--tw-prose-links': colors.lime[600],
1286 | '--tw-prose-invert-links': colors.lime[500],
1287 | },
1288 | },
1289 |
1290 | green: {
1291 | css: {
1292 | '--tw-prose-links': colors.green[600],
1293 | '--tw-prose-invert-links': colors.green[500],
1294 | },
1295 | },
1296 |
1297 | emerald: {
1298 | css: {
1299 | '--tw-prose-links': colors.emerald[600],
1300 | '--tw-prose-invert-links': colors.emerald[500],
1301 | },
1302 | },
1303 |
1304 | teal: {
1305 | css: {
1306 | '--tw-prose-links': colors.teal[600],
1307 | '--tw-prose-invert-links': colors.teal[500],
1308 | },
1309 | },
1310 |
1311 | cyan: {
1312 | css: {
1313 | '--tw-prose-links': colors.cyan[600],
1314 | '--tw-prose-invert-links': colors.cyan[500],
1315 | },
1316 | },
1317 |
1318 | sky: {
1319 | css: {
1320 | '--tw-prose-links': colors.sky[600],
1321 | '--tw-prose-invert-links': colors.sky[500],
1322 | },
1323 | },
1324 |
1325 | blue: {
1326 | css: {
1327 | '--tw-prose-links': colors.blue[600],
1328 | '--tw-prose-invert-links': colors.blue[500],
1329 | },
1330 | },
1331 |
1332 | indigo: {
1333 | css: {
1334 | '--tw-prose-links': colors.indigo[600],
1335 | '--tw-prose-invert-links': colors.indigo[500],
1336 | },
1337 | },
1338 |
1339 | violet: {
1340 | css: {
1341 | '--tw-prose-links': colors.violet[600],
1342 | '--tw-prose-invert-links': colors.violet[500],
1343 | },
1344 | },
1345 |
1346 | purple: {
1347 | css: {
1348 | '--tw-prose-links': colors.purple[600],
1349 | '--tw-prose-invert-links': colors.purple[500],
1350 | },
1351 | },
1352 |
1353 | fuchsia: {
1354 | css: {
1355 | '--tw-prose-links': colors.fuchsia[600],
1356 | '--tw-prose-invert-links': colors.fuchsia[500],
1357 | },
1358 | },
1359 |
1360 | pink: {
1361 | css: {
1362 | '--tw-prose-links': colors.pink[600],
1363 | '--tw-prose-invert-links': colors.pink[500],
1364 | },
1365 | },
1366 |
1367 | rose: {
1368 | css: {
1369 | '--tw-prose-links': colors.rose[600],
1370 | '--tw-prose-invert-links': colors.rose[500],
1371 | },
1372 | },
1373 |
1374 | // Invert (for dark mode)
1375 | invert: {
1376 | css: {
1377 | '--tw-prose-body': 'var(--tw-prose-invert-body)',
1378 | '--tw-prose-headings': 'var(--tw-prose-invert-headings)',
1379 | '--tw-prose-lead': 'var(--tw-prose-invert-lead)',
1380 | '--tw-prose-links': 'var(--tw-prose-invert-links)',
1381 | '--tw-prose-bold': 'var(--tw-prose-invert-bold)',
1382 | '--tw-prose-counters': 'var(--tw-prose-invert-counters)',
1383 | '--tw-prose-bullets': 'var(--tw-prose-invert-bullets)',
1384 | '--tw-prose-hr': 'var(--tw-prose-invert-hr)',
1385 | '--tw-prose-quotes': 'var(--tw-prose-invert-quotes)',
1386 | '--tw-prose-quote-borders': 'var(--tw-prose-invert-quote-borders)',
1387 | '--tw-prose-captions': 'var(--tw-prose-invert-captions)',
1388 | '--tw-prose-kbd': 'var(--tw-prose-invert-kbd)',
1389 | '--tw-prose-kbd-shadows': 'var(--tw-prose-invert-kbd-shadows)',
1390 | '--tw-prose-code': 'var(--tw-prose-invert-code)',
1391 | '--tw-prose-pre-code': 'var(--tw-prose-invert-pre-code)',
1392 | '--tw-prose-pre-bg': 'var(--tw-prose-invert-pre-bg)',
1393 | '--tw-prose-th-borders': 'var(--tw-prose-invert-th-borders)',
1394 | '--tw-prose-td-borders': 'var(--tw-prose-invert-td-borders)',
1395 | },
1396 | },
1397 | }
1398 |
1399 | module.exports = {
1400 | DEFAULT: {
1401 | css: [
1402 | {
1403 | color: 'var(--tw-prose-body)',
1404 | maxWidth: '65ch',
1405 | p: {}, // Required to maintain correct order when merging
1406 | '[class~="lead"]': {
1407 | color: 'var(--tw-prose-lead)',
1408 | },
1409 | a: {
1410 | color: 'var(--tw-prose-links)',
1411 | textDecoration: 'underline',
1412 | fontWeight: '500',
1413 | },
1414 | strong: {
1415 | color: 'var(--tw-prose-bold)',
1416 | fontWeight: '600',
1417 | },
1418 | 'a strong': {
1419 | color: 'inherit',
1420 | },
1421 | 'blockquote strong': {
1422 | color: 'inherit',
1423 | },
1424 | 'thead th strong': {
1425 | color: 'inherit',
1426 | },
1427 | ol: {
1428 | listStyleType: 'decimal',
1429 | },
1430 | 'ol[type="A"]': {
1431 | listStyleType: 'upper-alpha',
1432 | },
1433 | 'ol[type="a"]': {
1434 | listStyleType: 'lower-alpha',
1435 | },
1436 | 'ol[type="A" s]': {
1437 | listStyleType: 'upper-alpha',
1438 | },
1439 | 'ol[type="a" s]': {
1440 | listStyleType: 'lower-alpha',
1441 | },
1442 | 'ol[type="I"]': {
1443 | listStyleType: 'upper-roman',
1444 | },
1445 | 'ol[type="i"]': {
1446 | listStyleType: 'lower-roman',
1447 | },
1448 | 'ol[type="I" s]': {
1449 | listStyleType: 'upper-roman',
1450 | },
1451 | 'ol[type="i" s]': {
1452 | listStyleType: 'lower-roman',
1453 | },
1454 | 'ol[type="1"]': {
1455 | listStyleType: 'decimal',
1456 | },
1457 | ul: {
1458 | listStyleType: 'disc',
1459 | },
1460 | 'ol > li::marker': {
1461 | fontWeight: '400',
1462 | color: 'var(--tw-prose-counters)',
1463 | },
1464 | 'ul > li::marker': {
1465 | color: 'var(--tw-prose-bullets)',
1466 | },
1467 | dt: {
1468 | color: 'var(--tw-prose-headings)',
1469 | fontWeight: '600',
1470 | },
1471 | hr: {
1472 | borderColor: 'var(--tw-prose-hr)',
1473 | borderTopWidth: '1px',
1474 | },
1475 | blockquote: {
1476 | fontWeight: '500',
1477 | fontStyle: 'italic',
1478 | color: 'var(--tw-prose-quotes)',
1479 | borderInlineStartWidth: '0.25rem',
1480 | borderInlineStartColor: 'var(--tw-prose-quote-borders)',
1481 | quotes: '"\\201C""\\201D""\\2018""\\2019"',
1482 | },
1483 | 'blockquote p:first-of-type::before': {
1484 | content: 'open-quote',
1485 | },
1486 | 'blockquote p:last-of-type::after': {
1487 | content: 'close-quote',
1488 | },
1489 | h1: {
1490 | color: 'var(--tw-prose-headings)',
1491 | fontWeight: '800',
1492 | },
1493 | 'h1 strong': {
1494 | fontWeight: '900',
1495 | color: 'inherit',
1496 | },
1497 | h2: {
1498 | color: 'var(--tw-prose-headings)',
1499 | fontWeight: '700',
1500 | },
1501 | 'h2 strong': {
1502 | fontWeight: '800',
1503 | color: 'inherit',
1504 | },
1505 | h3: {
1506 | color: 'var(--tw-prose-headings)',
1507 | fontWeight: '600',
1508 | },
1509 | 'h3 strong': {
1510 | fontWeight: '700',
1511 | color: 'inherit',
1512 | },
1513 | h4: {
1514 | color: 'var(--tw-prose-headings)',
1515 | fontWeight: '600',
1516 | },
1517 | 'h4 strong': {
1518 | fontWeight: '700',
1519 | color: 'inherit',
1520 | },
1521 | img: {}, // Required to maintain correct order when merging
1522 | picture: {
1523 | display: 'block',
1524 | },
1525 | video: {}, // Required to maintain correct order when merging
1526 | kbd: {
1527 | fontWeight: '500',
1528 | fontFamily: 'inherit',
1529 | color: 'var(--tw-prose-kbd)',
1530 | boxShadow:
1531 | '0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%)',
1532 | },
1533 | code: {
1534 | color: 'var(--tw-prose-code)',
1535 | fontWeight: '600',
1536 | },
1537 | 'code::before': {
1538 | content: '"`"',
1539 | },
1540 | 'code::after': {
1541 | content: '"`"',
1542 | },
1543 | 'a code': {
1544 | color: 'inherit',
1545 | },
1546 | 'h1 code': {
1547 | color: 'inherit',
1548 | },
1549 | 'h2 code': {
1550 | color: 'inherit',
1551 | },
1552 | 'h3 code': {
1553 | color: 'inherit',
1554 | },
1555 | 'h4 code': {
1556 | color: 'inherit',
1557 | },
1558 | 'blockquote code': {
1559 | color: 'inherit',
1560 | },
1561 | 'thead th code': {
1562 | color: 'inherit',
1563 | },
1564 | pre: {
1565 | color: 'var(--tw-prose-pre-code)',
1566 | backgroundColor: 'var(--tw-prose-pre-bg)',
1567 | overflowX: 'auto',
1568 | fontWeight: '400',
1569 | },
1570 | 'pre code': {
1571 | backgroundColor: 'transparent',
1572 | borderWidth: '0',
1573 | borderRadius: '0',
1574 | padding: '0',
1575 | fontWeight: 'inherit',
1576 | color: 'inherit',
1577 | fontSize: 'inherit',
1578 | fontFamily: 'inherit',
1579 | lineHeight: 'inherit',
1580 | },
1581 | 'pre code::before': {
1582 | content: 'none',
1583 | },
1584 | 'pre code::after': {
1585 | content: 'none',
1586 | },
1587 | table: {
1588 | width: '100%',
1589 | tableLayout: 'auto',
1590 | marginTop: em(32, 16),
1591 | marginBottom: em(32, 16),
1592 | },
1593 | thead: {
1594 | borderBottomWidth: '1px',
1595 | borderBottomColor: 'var(--tw-prose-th-borders)',
1596 | },
1597 | 'thead th': {
1598 | color: 'var(--tw-prose-headings)',
1599 | fontWeight: '600',
1600 | verticalAlign: 'bottom',
1601 | },
1602 | 'tbody tr': {
1603 | borderBottomWidth: '1px',
1604 | borderBottomColor: 'var(--tw-prose-td-borders)',
1605 | },
1606 | 'tbody tr:last-child': {
1607 | borderBottomWidth: '0',
1608 | },
1609 | 'tbody td': {
1610 | verticalAlign: 'baseline',
1611 | },
1612 | tfoot: {
1613 | borderTopWidth: '1px',
1614 | borderTopColor: 'var(--tw-prose-th-borders)',
1615 | },
1616 | 'tfoot td': {
1617 | verticalAlign: 'top',
1618 | },
1619 | 'th, td': {
1620 | textAlign: 'start',
1621 | },
1622 | 'figure > *': {}, // Required to maintain correct order when merging
1623 | figcaption: {
1624 | color: 'var(--tw-prose-captions)',
1625 | },
1626 | },
1627 | defaultModifiers.gray.css,
1628 | ...defaultModifiers.base.css,
1629 | ],
1630 | },
1631 | ...defaultModifiers,
1632 | }
1633 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const isPlainObject = require('lodash.isplainobject')
2 |
3 | const parser = require('postcss-selector-parser')
4 | const parseSelector = parser()
5 |
6 | module.exports = {
7 | isUsableColor(color, values) {
8 | return isPlainObject(values) && color !== 'gray' && values[600]
9 | },
10 |
11 | /**
12 | * @param {string} selector
13 | */
14 | commonTrailingPseudos(selector) {
15 | let ast = parseSelector.astSync(selector)
16 |
17 | /** @type {import('postcss-selector-parser').Pseudo[][]} */
18 | let matrix = []
19 |
20 | // Put the pseudo elements in reverse order in a sparse, column-major 2D array
21 | for (let [i, sel] of ast.nodes.entries()) {
22 | for (const [j, child] of [...sel.nodes].reverse().entries()) {
23 | // We only care about pseudo elements
24 | if (child.type !== 'pseudo' || !child.value.startsWith('::')) {
25 | break
26 | }
27 |
28 | matrix[j] = matrix[j] || []
29 | matrix[j][i] = child
30 | }
31 | }
32 |
33 | let trailingPseudos = parser.selector()
34 |
35 | // At this point the pseudo elements are in a column-major 2D array
36 | // This means each row contains one "column" of pseudo elements from each selector
37 | // We can compare all the pseudo elements in a row to see if they are the same
38 | for (const pseudos of matrix) {
39 | // It's a sparse 2D array so there are going to be holes in the rows
40 | // We skip those
41 | if (!pseudos) {
42 | continue
43 | }
44 |
45 | let values = new Set(pseudos.map((p) => p.value))
46 |
47 | // The pseudo elements are not the same
48 | if (values.size > 1) {
49 | break
50 | }
51 |
52 | pseudos.forEach((pseudo) => pseudo.remove())
53 | trailingPseudos.prepend(pseudos[0])
54 | }
55 |
56 | if (trailingPseudos.nodes.length) {
57 | return [trailingPseudos.toString(), ast.toString()]
58 | }
59 |
60 | return [null, selector]
61 | },
62 | }
63 |
--------------------------------------------------------------------------------