tailwind_css_fixes/systems/preflight/
mod.rs

1use std::fmt::{Display, Formatter};
2
3/// Tailwind CSS Preflight v4 Style System
4/// <https://tailwindcss.com/docs/preflight>
5/// https://github.com/tailwindlabs/tailwindcss/blob/88b9f15b65588a87c5b6b13316530b4aecbc1b0b/packages/tailwindcss/preflight.css
6#[derive(Clone, Debug)]
7pub struct PreflightSystem {
8    /// Disables all preflight styles if set to true.
9    pub disable: bool,
10    /// Resets box-sizing, margins, padding, and borders for all elements.
11    pub global_reset: bool,
12    /// Applies consistent line-height, font-family, and other root-level styles.
13    pub html_base: bool,
14    /// Unstyles headings (`h1`-`h6`) to inherit font size and weight.
15    pub unstyle_headings: bool,
16    /// Resets link colors and text decoration to inherit from their parent.
17    pub unstyle_links: bool,
18    /// Removes default list styles (`list-style: none`) and resets margin/padding.
19    pub unstyle_lists: bool,
20    /// Makes images and other replaced elements `display: block`.
21    pub block_level_media: bool,
22    /// Resets table styles for border-collapse and text-indent.
23    pub reset_tables: bool,
24    /// Applies a comprehensive reset to form elements like buttons, inputs, and textareas.
25    pub reset_forms: bool,
26    /// Prevents `hidden` elements from being displayed.
27    pub hidden_attribute: bool,
28
29    /// Includes minor typographic styles and element-specific enhancements.
30    /// This provides sensible defaults for elements like `<strong>`, `<code>`, `<abbr>`,
31    /// and fixes issues such as `<sub>` and `<sup>` affecting line height.
32    pub specific_extras: bool,
33
34    /// Includes a collection of hyper-specific browser compatibility fixes.
35    /// This is primarily focused on normalizing the appearance of complex form controls,
36    /// addressing quirks in WebKit date pickers, search inputs, number input buttons,
37    /// and Firefox's focus and invalid input styles.
38    pub compatibility_fixes: bool,
39    
40    /// User-defined custom CSS to be prepended to the preflight styles.
41    pub custom: String,
42}
43
44impl Default for PreflightSystem {
45    /// The default state includes the COMPLETE set of Preflight styles.
46    fn default() -> Self {
47        Self {
48            disable: false,
49            global_reset: true,
50            html_base: true,
51            unstyle_headings: true,
52            unstyle_links: true,
53            unstyle_lists: true,
54            block_level_media: true,
55            reset_tables: true,
56            reset_forms: true,
57            hidden_attribute: true,
58            specific_extras: true,
59            compatibility_fixes: true,
60            custom: String::new(),
61        }
62    }
63}
64
65impl PreflightSystem {
66    const GLOBAL_RESET: &'static str = r#"
67*,
68::after,
69::before,
70::backdrop,
71::file-selector-button {
72  box-sizing: border-box; /* 1 */
73  margin: 0; /* 2 */
74  padding: 0; /* 2 */
75  border: 0 solid; /* 3 */
76}
77"#;
78
79    const HTML_BASE: &'static str = r#"
80html,
81:host {
82  line-height: 1.5;
83  -webkit-text-size-adjust: 100%;
84  tab-size: 4;
85  font-family: --theme(
86    --default-font-family,
87    ui-sans-serif,
88    system-ui,
89    sans-serif,
90    'Apple Color Emoji',
91    'Segoe UI Emoji',
92    'Segoe UI Symbol',
93    'Noto Color Emoji'
94  );
95  font-feature-settings: --theme(--default-font-feature-settings, normal);
96  font-variation-settings: --theme(--default-font-variation-settings, normal);
97  -webkit-tap-highlight-color: transparent;
98}
99hr {
100  height: 0;
101  color: inherit;
102  border-top-width: 1px;
103}
104"#;
105
106    const UNSTYLE_HEADINGS: &'static str = r#"
107h1,
108h2,
109h3,
110h4,
111h5,
112h6 {
113  font-size: inherit;
114  font-weight: inherit;
115}
116"#;
117
118    const UNSTYLE_LINKS: &'static str = r#"
119a {
120  color: inherit;
121  -webkit-text-decoration: inherit;
122  text-decoration: inherit;
123}
124"#;
125
126    const UNSTYLE_LISTS: &'static str = r#"
127ol,
128ul,
129menu {
130  list-style: none;
131}
132"#;
133
134    const BLOCK_LEVEL_MEDIA: &'static str = r#"
135img,
136svg,
137video,
138canvas,
139audio,
140iframe,
141embed,
142object {
143  display: block;
144  vertical-align: middle;
145}
146img,
147video {
148  max-width: 100%;
149  height: auto;
150}
151"#;
152
153    const RESET_TABLES: &'static str = r#"
154table {
155  text-indent: 0;
156  border-color: inherit;
157  border-collapse: collapse;
158}
159"#;
160
161    const RESET_FORMS: &'static str = r#"
162button,
163input,
164select,
165optgroup,
166textarea,
167::file-selector-button {
168  font: inherit;
169  font-feature-settings: inherit;
170  font-variation-settings: inherit;
171  letter-spacing: inherit;
172  color: inherit;
173  border-radius: 0;
174  background-color: transparent;
175  opacity: 1;
176}
177::placeholder {
178  opacity: 1;
179}
180@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
181  ::placeholder {
182    color: color-mix(in oklab, currentcolor 50%, transparent);
183  }
184}
185textarea {
186  resize: vertical;
187}
188button,
189input:where([type='button'], [type='reset'], [type='submit']),
190::file-selector-button {
191  appearance: button;
192}
193"#;
194
195    const HIDDEN_ATTRIBUTE: &'static str = r#"
196[hidden]:where(:not([hidden='until-found'])) {
197  display: none !important;
198}
199"#;
200
201
202
203    const PREFLIGHT_EXTRAS: &'static str = r#"
204/* Minor typographic and element styles */
205abbr:where([title]) { -webkit-text-decoration: underline dotted; text-decoration: underline dotted; }
206b, strong { font-weight: bolder; }
207code, kbd, samp, pre { font-family: --theme(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace); font-size: 1em; }
208small { font-size: 80%; }
209sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; }
210sub { bottom: -0.25em; }
211sup { top: -0.5em; }
212progress { vertical-align: baseline; }
213summary { display: list-item; }
214"#;
215
216
217    const PREFLIGHT_COMPATIBILITY_FIXES: &'static str = r#"
218/* Browser-specific compatibility fixes */
219:-moz-focusring { outline: auto; }
220:-moz-ui-invalid { box-shadow: none; }
221:where(select:is([multiple], [size])) optgroup { font-weight: bolder; }
222:where(select:is([multiple], [size])) optgroup option { padding-inline-start: 20px; }
223::file-selector-button { margin-inline-end: 4px; }
224::-webkit-search-decoration { -webkit-appearance: none; }
225::-webkit-date-and-time-value { min-height: 1lh; text-align: inherit; }
226::-webkit-datetime-edit { display: inline-flex; }
227::-webkit-datetime-edit-fields-wrapper, ::-webkit-datetime-edit-fields-wrapper, ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { padding-block: 0; }
228::-webkit-calendar-picker-indicator { line-height: 1; }
229::-webkit-inner-spin-button, ::-webkit-outer-spin-button { height: auto; }
230"#;
231
232
233
234    // Creates a Preflight instance with the COMPLETE set of Tailwind v4 styles.
235    pub fn full() -> Self {
236        Self::default()
237    }
238
239
240    /// Creates a Preflight instance without the "extras" and "compatibility fixes" styles.
241    pub fn core() -> Self {
242        Self {
243            specific_extras: false,
244            compatibility_fixes: false,
245            ..Self::default()
246        }
247    }
248
249    /// Appends custom CSS to the end of the preflight styles.
250    pub fn add_custom(&mut self, custom: &str) {
251        if !self.custom.is_empty() {
252            self.custom.push('\n');
253        }
254        self.custom.push_str(custom);
255    }
256  }
257
258
259impl Display for PreflightSystem {
260    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
261        if self.disable {
262            return Ok(());
263        }
264        
265        // Generate the built-in styles based on the boolean flags
266        if self.global_reset {
267            writeln!(f, "{}", Self::GLOBAL_RESET.trim())?;
268        }
269        if self.html_base {
270            writeln!(f, "{}", Self::HTML_BASE.trim())?;
271        }
272        if self.unstyle_headings {
273            writeln!(f, "{}", Self::UNSTYLE_HEADINGS.trim())?;
274        }
275        if self.unstyle_links {
276            writeln!(f, "{}", Self::UNSTYLE_LINKS.trim())?;
277        }
278        if self.unstyle_lists {
279            writeln!(f, "{}", Self::UNSTYLE_LISTS.trim())?;
280        }
281        if self.block_level_media {
282            writeln!(f, "{}", Self::BLOCK_LEVEL_MEDIA.trim())?;
283        }
284        if self.reset_tables {
285            writeln!(f, "{}", Self::RESET_TABLES.trim())?;
286        }
287        if self.reset_forms {
288            writeln!(f, "{}", Self::RESET_FORMS.trim())?;
289        }
290        if self.hidden_attribute {
291            writeln!(f, "{}", Self::HIDDEN_ATTRIBUTE.trim())?;
292        }
293        if self.specific_extras {
294            writeln!(f, "{}", Self::PREFLIGHT_EXTRAS.trim())?;
295        }
296        if self.compatibility_fixes {
297            writeln!(f, "{}", Self::PREFLIGHT_COMPATIBILITY_FIXES.trim())?;
298        }
299
300        // Append the user's custom styles at the very end
301        if !self.custom.is_empty() {
302            writeln!(f, "{}", self.custom.trim())?;
303        }
304
305        Ok(())
306    }
307}