@@ -30,6 +30,9 @@ const ATTR_RENAMES: &[(&str, &str)] = &[
3030 ("animate", "animation"),
3131 ("inset-area", "position-area"),
3232 ("align-center", "center-x"),
33+ ("gap-x", "column-gap"),
34+ ("gap-y", "row-gap"),
35+ ("shadow", "box-shadow"),
3336];
3437
3538/// Variable filter aliases (`$x|upper`) and their canonical names.
@@ -718,21 +721,48 @@ fn rewrite_attr_list(list: &str, element_attrs: bool) -> String {
718721 }
719722 let mut out = String::with_capacity(list.len());
720723 let mut first = true;
724+ let mut dropped_leading = false;
721725 for segment in split_top_level_commas(list) {
726+ // `critical` (inline styles) was removed without a replacement
727+ if segment.trim() == "critical" {
728+ dropped_leading |= first;
729+ continue;
730+ }
731+ let lead = segment.len() - segment.trim_start().len();
732+ let (ws, body) = segment.split_at(lead);
722733 if !first {
723734 out.push(',');
735+ out.push_str(ws);
736+ } else if !dropped_leading || ws.contains('\n') {
737+ out.push_str(ws);
724738 }
725739 first = false;
726− let lead = segment.len() - segment.trim_start().len();
727− let (ws, body) = segment.split_at(lead);
728− out.push_str(ws);
729740 // `...$bundle` spread → `$bundle`
730741 let body = body.strip_prefix("...$").map_or(body.to_string(), |b| format!("${b}"));
731742 // Built-in style attributes that became standard-library bundles
732743 let body = match body.trim_end() {
733− "skeleton" | "no-scrollbar" => format!("${}", body),
744+ "skeleton" | "no-scrollbar" | "truncate" => format!("${}", body),
734745 _ => body,
735746 };
747+ // `blur N` / `backdrop-blur N` → the filter they generated
748+ let body = {
749+ let (key, value) = body.split_once(' ').unwrap_or((&body, ""));
750+ let (prefix, base) = key.rsplit_once(':').map_or(("", key), |(p, b)| (p, b));
751+ let prefix = if prefix.is_empty() { String::new() } else { format!("{prefix}:") };
752+ let px = |v: &str| {
753+ let v = v.trim();
754+ if !v.is_empty() && v.parse::<f64>().is_ok() && v != "0" {
755+ format!("{v}px")
756+ } else {
757+ v.to_string()
758+ }
759+ };
760+ match base {
761+ "blur" => format!("{prefix}filter blur({})", px(value)),
762+ "backdrop-blur" => format!("{prefix}backdrop-filter blur({})", px(value)),
763+ _ => body.clone(),
764+ }
765+ };
736766 // `gradient A B [ANGLE]` → the `background` it generated
737767 let body = match body.strip_prefix("gradient ") {
738768 Some(v) => {
@@ -1095,6 +1125,15 @@ mod tests {
10951125 assert_eq!(upgrade("@fetch $d http://x").manual.len(), 1);
10961126 }
10971127
1128+ #[test]
1129+ fn css_alias_attributes() {
1130+ assert_eq!(
1131+ up("@el [gap-x 4, hover:shadow 0 1px red, blur 4, backdrop-blur 2px, truncate, critical]"),
1132+ "@el [column-gap 4, hover:box-shadow 0 1px red, filter blur(4px), backdrop-filter blur(2px), $truncate]"
1133+ );
1134+ assert_eq!(up("@el [critical, padding 4]"), "@el [padding 4]");
1135+ }
1136+
10981137 #[test]
10991138 fn verbatim_regions_are_untouched() {
11001139 let src = "@script\n for (x of y) {}\n@raw \"\"\"\n@unless\n\"\"\"\n@style\n .p { }";