@@ -1351,113 +1351,14 @@ fn generate_node(
1354−/// HTML attributes that are passed through to the HTML tag rather than converted to CSS.
1355−const HTML_PASSTHROUGH_ATTRS: &[&str] = &[
1388− // Image optimization
1391− // Media src (explicit attribute form)
1393− // New element attributes
1414− "popovertargetaction",
1415− // Modern form/input hints
1418− // Performance hints
1424− // ARIA live regions
1430−/// Boolean HTML attributes (rendered without a value, e.g., `<input disabled>`).
1431−const BOOLEAN_HTML_ATTRS: &[&str] = &[
1451fn emit_html_passthrough_attrs(out: &mut String, attrs: &[Attribute]) {
1355fn emit_html_passthrough_attrs(out: &mut String, attrs: &[Attribute]) {
1452 for attr in attrs {
1356 for attr in attrs {
1453 let key = attr.key.as_str();
1357 let key = attr.key.as_str();
1454− let is_passthrough = HTML_PASSTHROUGH_ATTRS.contains(&key)
1358+ if !crate::vocab::is_html_passthrough(key) {
1455− || key.starts_with("aria-")
1456− || key.starts_with("data-");
1457− if !is_passthrough {
1458 continue;
1359 continue;
1460− if BOOLEAN_HTML_ATTRS.contains(&key) && attr.value.is_none() {
1361+ if crate::vocab::BOOLEAN_HTML_ATTRS.contains(&key) && attr.value.is_none() {
1461 out.push(' ');
1362 out.push(' ');
1462 out.push_str(key);
1363 out.push_str(key);
1463 } else if let Some(val) = &attr.value {
1364 } else if let Some(val) = &attr.value {
@@ -2706,16 +2607,6 @@ fn attrs_to_css(
2708 // Style
2609 // Style
2710− if let Some(v) = val {
2711− push_css(&mut css, "background", v);
2715− if let Some(v) = val {
2716− push_css(&mut css, "color", v);
2719 "border" => {
2610 "border" => {
2720 if let Some(v) = val {
2611 if let Some(v) = val {
2721 let parts: Vec<&str> = v.splitn(2, ' ').collect();
2612 let parts: Vec<&str> = v.splitn(2, ' ').collect();
@@ -2776,28 +2667,8 @@ fn attrs_to_css(
2776 push_css(&mut css, "font-family", v);
2667 push_css(&mut css, "font-family", v);
2780− if let Some(v) = val {
2781− push_css(&mut css, "transition", v);
2785− if let Some(v) = val {
2786− push_css(&mut css, "cursor", v);
2790− if let Some(v) = val {
2791− push_css(&mut css, "opacity", v);
2795 // Typography
2671 // Typography
2797− if let Some(v) = val {
2798− push_css(&mut css, "text-align", v);
2801 "line-height" => {
2672 "line-height" => {
2802 if let Some(v) = val {
2673 if let Some(v) = val {
2803 push_css(&mut css, "line-height", &css_line_height(v));
2674 push_css(&mut css, "line-height", &css_line_height(v));
@@ -2808,28 +2679,8 @@ fn attrs_to_css(
2808 push_css(&mut css, "letter-spacing", &css_px(v));
2679 push_css(&mut css, "letter-spacing", &css_px(v));
2811− "text-transform" => {
2812− if let Some(v) = val {
2813− push_css(&mut css, "text-transform", v);
2817− if let Some(v) = val {
2818− push_css(&mut css, "white-space", v);
2822 // Overflow & positioning
2683 // Overflow & positioning
2824− if let Some(v) = val {
2825− push_css(&mut css, "overflow", v);
2829− if let Some(v) = val {
2830− push_css(&mut css, "position", v);
2833 "top" => {
2684 "top" => {
2834 if let Some(v) = val {
2685 if let Some(v) = val {
2835 push_css(&mut css, "top", &css_px(v));
2686 push_css(&mut css, "top", &css_px(v));
@@ -2850,37 +2701,10 @@ fn attrs_to_css(
2850 push_css(&mut css, "left", &css_px(v));
2701 push_css(&mut css, "left", &css_px(v));
2854− if let Some(v) = val {
2855− push_css(&mut css, "z-index", v);
2859 // Display & visibility
2705 // Display & visibility
2861− if let Some(v) = val {
2862− push_css(&mut css, "display", v);
2866− if let Some(v) = val {
2867− push_css(&mut css, "visibility", v);
2870 "hidden" => push_css(&mut css, "display", "none"),
2706 "hidden" => push_css(&mut css, "display", "none"),
2872− // Transform & filters
2874− if let Some(v) = val {
2875− push_css(&mut css, "transform", v);
2878− "backdrop-filter" => {
2879− if let Some(v) = val {
2880− push_css(&mut css, "backdrop-filter", v);
2884 // Effects
2708 // Effects
2885 "shadow" => {
2709 "shadow" => {
2886 if let Some(v) = val {
2710 if let Some(v) = val {
@@ -2942,20 +2766,6 @@ fn attrs_to_css(
2947− if let Some(v) = val {
2948− push_css(&mut css, "animation", v);
2953− "aspect-ratio" => {
2954− if let Some(v) = val {
2955− push_css(&mut css, "aspect-ratio", v);
2959 // CSS containment for rendering performance
2769 // CSS containment for rendering performance
2960 "contain" => {
2770 "contain" => {
2961 if let Some(v) = val {
2771 if let Some(v) = val {
@@ -3080,18 +2890,6 @@ fn attrs_to_css(
3084− "scroll-snap-type" => {
3085− if let Some(v) = val {
3086− push_css(&mut css, "scroll-snap-type", v);
3089− "scroll-snap-align" => {
3090− if let Some(v) = val {
3091− push_css(&mut css, "scroll-snap-align", v);
3095 // Margin
2893 // Margin
3096 "margin" => {
2894 "margin" => {
3097 if let Some(v) = val {
2895 if let Some(v) = val {
@@ -3109,125 +2907,10 @@ fn attrs_to_css(
3114− if let Some(v) = val {
3115− push_css(&mut css, "filter", v);
3119− // Object fit/position (for images)
3121− if let Some(v) = val {
3122− push_css(&mut css, "object-fit", v);
3125− "object-position" => {
3126− if let Some(v) = val {
3127− push_css(&mut css, "object-position", v);
3133− if let Some(v) = val {
3134− push_css(&mut css, "text-shadow", v);
3139− "text-overflow" => {
3140− if let Some(v) = val {
3141− push_css(&mut css, "text-overflow", v);
3146− "pointer-events" => {
3147− if let Some(v) = val {
3148− push_css(&mut css, "pointer-events", v);
3152− if let Some(v) = val {
3153− push_css(&mut css, "user-select", v);
3157− // Flexbox/grid alignment
3158− "justify-content" => {
3159− if let Some(v) = val {
3160− push_css(&mut css, "justify-content", v);
3164− if let Some(v) = val {
3165− push_css(&mut css, "align-items", v);
3171− if let Some(v) = val {
3172− push_css(&mut css, "order", v);
3176− // Background extras
3177− "background-size" => {
3178− if let Some(v) = val {
3179− push_css(&mut css, "background-size", v);
3182− "background-position" => {
3183− if let Some(v) = val {
3184− push_css(&mut css, "background-position", v);
3187− "background-repeat" => {
3188− if let Some(v) = val {
3189− push_css(&mut css, "background-repeat", v);
3195− if let Some(v) = val {
3196− push_css(&mut css, "word-break", v);
3199− "overflow-wrap" => {
3200− if let Some(v) = val {
3201− push_css(&mut css, "overflow-wrap", v);
3205 // Container queries
2910 // Container queries
3206 "container" => {
2911 "container" => {
3207 push_css(&mut css, "container-type", "inline-size");
2912 push_css(&mut css, "container-type", "inline-size");
3209− "container-name" => {
3210− if let Some(v) = val {
3211− push_css(&mut css, "container-name", v);
3214− "container-type" => {
3215− if let Some(v) = val {
3216− push_css(&mut css, "container-type", v);
3222− if let Some(v) = val {
3223− push_css(&mut css, "overflow-x", v);
3227− if let Some(v) = val {
3228− push_css(&mut css, "overflow-y", v);
3232 // Inset (shorthand for top/right/bottom/left)
2915 // Inset (shorthand for top/right/bottom/left)
3233 "inset" => {
2916 "inset" => {
@@ -3236,43 +2919,7 @@ fn attrs_to_css(
3239− // Accent & caret colors
3240− "accent-color" => {
3241− if let Some(v) = val {
3242− push_css(&mut css, "accent-color", v);
3246− if let Some(v) = val {
3247− push_css(&mut css, "caret-color", v);
3251− // Color scheme & appearance
3252− "color-scheme" => {
3253− if let Some(v) = val {
3254− push_css(&mut css, "color-scheme", v);
3258− if let Some(v) = val {
3259− push_css(&mut css, "appearance", v);
3265− if let Some(v) = val {
3266− push_css(&mut css, "list-style", v);
3270 // Table
2922 // Table
3271− "border-collapse" => {
3272− if let Some(v) = val {
3273− push_css(&mut css, "border-collapse", v);
3276 "border-spacing" => {
2923 "border-spacing" => {
3277 if let Some(v) = val {
2924 if let Some(v) = val {
3278 push_css(&mut css, "border-spacing", &css_px(v));
2925 push_css(&mut css, "border-spacing", &css_px(v));
@@ -3280,26 +2927,11 @@ fn attrs_to_css(
3282 // Text decoration
2929 // Text decoration
3283− "text-decoration" => {
3284− if let Some(v) = val {
3285− push_css(&mut css, "text-decoration", v);
3288− "text-decoration-color" => {
3289− if let Some(v) = val {
3290− push_css(&mut css, "text-decoration-color", v);
3293 "text-decoration-thickness" => {
2930 "text-decoration-thickness" => {
3294 if let Some(v) = val {
2931 if let Some(v) = val {
3295 push_css(&mut css, "text-decoration-thickness", &css_px(v));
2932 push_css(&mut css, "text-decoration-thickness", &css_px(v));
3298− "text-decoration-style" => {
3299− if let Some(v) = val {
3300− push_css(&mut css, "text-decoration-style", v);
3303 "text-underline-offset" => {
2935 "text-underline-offset" => {
3304 if let Some(v) = val {
2936 if let Some(v) = val {
3305 push_css(&mut css, "text-underline-offset", &css_px(v));
2937 push_css(&mut css, "text-underline-offset", &css_px(v));
@@ -3312,64 +2944,8 @@ fn attrs_to_css(
3312 push_css(&mut css, "column-width", &css_px(v));
2944 push_css(&mut css, "column-width", &css_px(v));
3316− if let Some(v) = val {
3317− push_css(&mut css, "column-rule", v);
3321− // Place items/self
3323− if let Some(v) = val {
3324− push_css(&mut css, "place-items", v);
3328− if let Some(v) = val {
3329− push_css(&mut css, "place-self", v);
3334− "scroll-behavior" => {
3335− if let Some(v) = val {
3336− push_css(&mut css, "scroll-behavior", v);
3342− if let Some(v) = val {
3343− push_css(&mut css, "resize", v);
3347 // New CSS properties
2948 // New CSS properties
3349− if let Some(v) = val {
3350− push_css(&mut css, "clip-path", v);
3353− "mix-blend-mode" => {
3354− if let Some(v) = val {
3355− push_css(&mut css, "mix-blend-mode", v);
3358− "background-blend-mode" => {
3359− if let Some(v) = val {
3360− push_css(&mut css, "background-blend-mode", v);
3363− "writing-mode" => {
3364− if let Some(v) = val {
3365− push_css(&mut css, "writing-mode", v);
3368− "column-count" => {
3369− if let Some(v) = val {
3370− push_css(&mut css, "column-count", v);
3373 "column-gap" => {
2949 "column-gap" => {
3374 if let Some(v) = val {
2950 if let Some(v) = val {
3375 push_css(&mut css, "column-gap", &css_px(v));
2951 push_css(&mut css, "column-gap", &css_px(v));
@@ -3380,71 +2956,11 @@ fn attrs_to_css(
3380 push_css(&mut css, "text-indent", &css_px(v));
2956 push_css(&mut css, "text-indent", &css_px(v));
3384− if let Some(v) = val {
3385− push_css(&mut css, "hyphens", v);
3389− if let Some(v) = val {
3390− push_css(&mut css, "flex-grow", v);
3394− if let Some(v) = val {
3395− push_css(&mut css, "flex-shrink", v);
3398 "flex-basis" => {
2959 "flex-basis" => {
3399 if let Some(v) = val {
2960 if let Some(v) = val {
3400 push_css(&mut css, "flex-basis", &css_px(v));
2961 push_css(&mut css, "flex-basis", &css_px(v));
3404− if let Some(v) = val {
3405− push_css(&mut css, "isolation", v);
3408− "place-content" => {
3409− if let Some(v) = val {
3410− push_css(&mut css, "place-content", v);
3413− "background-image" => {
3414− if let Some(v) = val {
3415− push_css(&mut css, "background-image", v);
3419− if let Some(v) = val {
3420− push_css(&mut css, "font-weight", v);
3424− if let Some(v) = val {
3425− push_css(&mut css, "font-style", v);
3429− if let Some(v) = val {
3430− push_css(&mut css, "text-wrap", v);
3434− if let Some(v) = val {
3435− push_css(&mut css, "will-change", v);
3438− "touch-action" => {
3439− if let Some(v) = val {
3440− push_css(&mut css, "touch-action", v);
3443− "vertical-align" => {
3444− if let Some(v) = val {
3445− push_css(&mut css, "vertical-align", v);
3448 "scroll-margin" => {
2964 "scroll-margin" => {
3449 if let Some(v) = val {
2965 if let Some(v) = val {
3450 push_css(&mut css, "scroll-margin", &css_px(v));
2966 push_css(&mut css, "scroll-margin", &css_px(v));
@@ -3471,11 +2987,6 @@ fn attrs_to_css(
3471 push_css(&mut css, effective_key, &css_px(v));
2987 push_css(&mut css, effective_key, &css_px(v));
3475− if let Some(v) = val {
3476− push_css(&mut css, "direction", v);
3480 "content" => {
2991 "content" => {
3481 if let Some(v) = val {
2992 if let Some(v) = val {
@@ -3554,94 +3065,6 @@ fn attrs_to_css(
3558− "grid-template-areas" => {
3559− if let Some(v) = val {
3560− push_css(&mut css, "grid-template-areas", v);
3564− if let Some(v) = val {
3565− push_css(&mut css, "grid-area", v);
3569− // View transitions
3570− "view-transition-name" => {
3571− if let Some(v) = val {
3572− push_css(&mut css, "view-transition-name", v);
3578− "grid-template-columns" => {
3579− if let Some(v) = val {
3580− push_css(&mut css, "grid-template-columns", v);
3583− "grid-template-rows" => {
3584− if let Some(v) = val {
3585− push_css(&mut css, "grid-template-rows", v);
3589− // Scroll-driven animations
3590− "animation-timeline" => {
3591− if let Some(v) = val {
3592− push_css(&mut css, "animation-timeline", v);
3595− "animation-range" => {
3596− if let Some(v) = val {
3597− push_css(&mut css, "animation-range", v);
3600− "view-timeline-name" => {
3601− if let Some(v) = val {
3602− push_css(&mut css, "view-timeline-name", v);
3605− "view-timeline-axis" => {
3606− if let Some(v) = val {
3607− push_css(&mut css, "view-timeline-axis", v);
3610− "scroll-timeline-name" => {
3611− if let Some(v) = val {
3612− push_css(&mut css, "scroll-timeline-name", v);
3615− "scroll-timeline-axis" => {
3616− if let Some(v) = val {
3617− push_css(&mut css, "scroll-timeline-axis", v);
3621− // Anchor positioning
3623− if let Some(v) = val {
3624− push_css(&mut css, "anchor-name", v);
3627− "position-anchor" => {
3628− if let Some(v) = val {
3629− push_css(&mut css, "position-anchor", v);
3632− "position-area" => {
3633− if let Some(v) = val {
3634− push_css(&mut css, "position-area", v);
3638− // initial-letter (drop caps)
3639− "initial-letter" => {
3640− if let Some(v) = val {
3641− push_css(&mut css, "initial-letter", v);
3645 // Critical CSS hint — not CSS, handled elsewhere
3068 // Critical CSS hint — not CSS, handled elsewhere
3646 "critical" => {}
3069 "critical" => {}
@@ -3657,6 +3080,18 @@ fn attrs_to_css(
3657 | "sandbox" | "allow" | "allowfullscreen" | "referrerpolicy" | "formaction"
3080 | "sandbox" | "allow" | "allowfullscreen" | "referrerpolicy" | "formaction"
3658 | "formmethod" | "formtarget" | "target" | "autofocus" => {}
3081 | "formmethod" | "formtarget" | "target" | "autofocus" => {}
3083+ // Any other standard CSS property is copied through, with `px`
3084+ // added to bare numbers where the property takes a length.
3085+ key if crate::vocab::is_css_property(key) && !crate::vocab::is_html_passthrough(key) => {
3086+ if let Some(v) = val {
3087+ if crate::vocab::is_length_property(key) {
3088+ push_css(&mut css, key, &css_px_multi(v));
3090+ push_css(&mut css, key, v);