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///  - minor typographic and element styles not included
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    /// Custom CSS to be prepended to the preflight styles.
29    pub custom: String,
30}
31
32impl Default for PreflightSystem {
33    fn default() -> Self {
34        Self {
35            disable: false,
36            global_reset: true,
37            html_base: true,
38            unstyle_headings: true,
39            unstyle_links: true,
40            unstyle_lists: true,
41            block_level_media: true,
42            reset_tables: true,
43            reset_forms: true,
44            hidden_attribute: true,
45            custom: String::new(),
46        }
47    }
48}
49
50impl PreflightSystem {
51    const GLOBAL_RESET: &'static str = r#"
52*,
53::after,
54::before,
55::backdrop,
56::file-selector-button {
57  box-sizing: border-box; /* 1 */
58  margin: 0; /* 2 */
59  padding: 0; /* 2 */
60  border: 0 solid; /* 3 */
61}
62"#;
63
64    const HTML_BASE: &'static str = r#"
65html,
66:host {
67  line-height: 1.5;
68  -webkit-text-size-adjust: 100%;
69  tab-size: 4;
70  font-family: --theme(
71    --default-font-family,
72    ui-sans-serif,
73    system-ui,
74    sans-serif,
75    'Apple Color Emoji',
76    'Segoe UI Emoji',
77    'Segoe UI Symbol',
78    'Noto Color Emoji'
79  );
80  font-feature-settings: --theme(--default-font-feature-settings, normal);
81  font-variation-settings: --theme(--default-font-variation-settings, normal);
82  -webkit-tap-highlight-color: transparent;
83}
84hr {
85  height: 0;
86  color: inherit;
87  border-top-width: 1px;
88}
89"#;
90
91    const UNSTYLE_HEADINGS: &'static str = r#"
92h1,
93h2,
94h3,
95h4,
96h5,
97h6 {
98  font-size: inherit;
99  font-weight: inherit;
100}
101"#;
102
103    const UNSTYLE_LINKS: &'static str = r#"
104a {
105  color: inherit;
106  -webkit-text-decoration: inherit;
107  text-decoration: inherit;
108}
109"#;
110
111    const UNSTYLE_LISTS: &'static str = r#"
112ol,
113ul,
114menu {
115  list-style: none;
116}
117"#;
118
119    const BLOCK_LEVEL_MEDIA: &'static str = r#"
120img,
121svg,
122video,
123canvas,
124audio,
125iframe,
126embed,
127object {
128  display: block;
129  vertical-align: middle;
130}
131img,
132video {
133  max-width: 100%;
134  height: auto;
135}
136"#;
137
138    const RESET_TABLES: &'static str = r#"
139table {
140  text-indent: 0;
141  border-color: inherit;
142  border-collapse: collapse;
143}
144"#;
145
146    const RESET_FORMS: &'static str = r#"
147button,
148input,
149select,
150optgroup,
151textarea,
152::file-selector-button {
153  font: inherit;
154  font-feature-settings: inherit;
155  font-variation-settings: inherit;
156  letter-spacing: inherit;
157  color: inherit;
158  border-radius: 0;
159  background-color: transparent;
160  opacity: 1;
161}
162::placeholder {
163  opacity: 1;
164}
165@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
166  ::placeholder {
167    color: color-mix(in oklab, currentcolor 50%, transparent);
168  }
169}
170textarea {
171  resize: vertical;
172}
173button,
174input:where([type='button'], [type='reset'], [type='submit']),
175::file-selector-button {
176  appearance: button;
177}
178"#;
179
180    const HIDDEN_ATTRIBUTE: &'static str = r#"
181[hidden]:where(:not([hidden='until-found'])) {
182  display: none !important;
183}
184"#;
185}
186
187impl Display for PreflightSystem {
188    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
189        if self.disable {
190            return Ok(());
191        }
192
193        f.write_str(&self.custom)?;
194        
195        if self.global_reset {
196            writeln!(f, "{}", Self::GLOBAL_RESET.trim())?;
197        }
198        if self.html_base {
199            writeln!(f, "{}", Self::HTML_BASE.trim())?;
200        }
201        if self.unstyle_headings {
202            writeln!(f, "{}", Self::UNSTYLE_HEADINGS.trim())?;
203        }
204        if self.unstyle_links {
205            writeln!(f, "{}", Self::UNSTYLE_LINKS.trim())?;
206        }
207        if self.unstyle_lists {
208            writeln!(f, "{}", Self::UNSTYLE_LISTS.trim())?;
209        }
210        if self.block_level_media {
211            writeln!(f, "{}", Self::BLOCK_LEVEL_MEDIA.trim())?;
212        }
213        if self.reset_tables {
214            writeln!(f, "{}", Self::RESET_TABLES.trim())?;
215        }
216        if self.reset_forms {
217            writeln!(f, "{}", Self::RESET_FORMS.trim())?;
218        }
219        if self.hidden_attribute {
220            writeln!(f, "{}", Self::HIDDEN_ATTRIBUTE.trim())?;
221        }
222
223        Ok(())
224    }
225}