Typed CSS attr(): Bezpośrednie wprowadzanie wartości atrybutów HTML do stylów
Dowiedz się, w jaki sposób funkcja CSS attr() przekształca atrybuty danych na wartości odpowiadające długościom, kolorom i czasom, dlaczego jej alternatywy nie działają, dlaczego funkcja url() jest zablokowana oraz jak bezpiecznie ją wykorzystywać obecnie.
Źródła: CSS attr() Reads Your HTML Now
Załóżmy sobie pasek postępu, którego wartość, 87, jest już zawarta w kodzie jako data-fill="87". Do niedawna, aby umieścić tę liczbę w atrybucie width, konieczny był krótki skrypt: odczytywanie wartości z dataset.fill, dodanie znaku procentowego oraz zapisanie tej wartości jako własności elementu. Wartość opuszczała dokument tylko po to, by zostać natychmiast ponownie do niego wprowadzona, przy czym wymagało to użycia skryptu oraz dodatkowej operacji renderowania.
Funkcja attr() eliminuje tę konieczność objazdu. Funkcja ta teraz przyjmuje typ danej wartości, dzięki czemu plik stylów może odczytać atrybut jako długość, kolor, liczbę lub identyfikator i wykorzystać go we wszelkich właściwościach, które akceptują ten typ. Niniejszy przewodnik omawia składnię, cztery sposoby rozwiązywania zapytań o wartości atrybutów, wzory zastępujące typowe skrypty, ograniczenia bezpieczeństwa dotyczące URL-ów oraz sposoby wdrożenia tej funkcjonalności, mimo że wsparcie przeglądarek jest jeszcze niepełne.
Od generatora ciągów do wartości typowanej
Klasyczna funkcja attr() działa we wszystkich głównych przeglądarkach od około 2015 roku, ale miała dokładnie jedno zadanie: generowanie ciągu znaków dla właściwości content, zwykle w pseudo-elementach.
/* the version you already know */
blockquote::after {
content: " — " attr(cite);
}
To przydatne do wyświetlania cytatu lub celu linku, i nic więcej. Umieszczenie starej funkcji attr() wewnątrz width powodowało odrzucenie całej deklaracji, ponieważ funkcja ta zwracała tylko ciągi znaków, a width nie potrzebuje takiej wartości.
Zmiany w CSS Values and Units Level 5 zmieniają te zasady: mówimy przeglądarce, jaki rodzaj wartości zawiera atrybut. Gdy silnik wie, że mamy do czynienia z długością, kolorem lub liczbą, wynik może być użyty wszędzie tam, gdzie dopuszczony jest ten typ, co w praktyce oznacza niemal każdą właściwość.
/* the version this article is about */
.bar { width: attr(data-fill %, 0%); }
.chip { background-color: attr(data-brand type(<color>), #888); }
.dial { rotate: attr(data-deg deg, 0deg); }
.card { view-transition-name: attr(id type(<custom-ident>), none); }
Dwie wyjaśnienia pomagają utrzymać realistyczne oczekiwania, ponieważ tę funkcjonalność łatwo przesadnie promować w obu kierunkach.
- To nie jest sposób na usunięcie JavaScripta z stylizacji. Zapisywanie
style="--fill: 87%"bezpośrednio w HTML nigdy nie wymagało skryptu. To, co jest nowe, dotyczy czegoś bardziej specyficznego: wykorzystanie wartości atrybutu w stylu, który wcześniej wymagał skryptu, a teraz już go nie wymaga. - To nie przenosi logiki aplikacji do CSS. Twoja aplikacja nadal decyduje, że zadanie jest w 87 procentach ukończone. Jedyną zmianą jest to, kto odczytuje tę wartość z elementu: kod, który napisałeś, lub plik stylów, który i tak miałby zostać zastosowany.
Anatomia funkcji
Gramatyka tego rozwiązania mieści się w jednej linii.
attr( <attr-name> <attr-type>? , <fallback-value>? )
Użyj attr(data-fill %, 0%). Składa się on z trzech części: nazwy atrybutu do odczytu, typu, w którym ma być on interpretowany, oraz wartości awaryjnej na wypadek, gdy wyszukiwanie nie przyniesie czegoś użytecznego.
Typ może być zapisany na trzy różne sposoby. Pierwszym jest type(), który umieszcza każdy typ danych CSS w nawiasach kwadratowych.
/* 1. type() — any CSS data type */
color: attr(data-brand type(<color>), crimson);
width: attr(data-w type(<length>), 0px);
scale: attr(data-s type(<number>), 1);
Drugim sposobem jest prosta jednostka, która oznacza „atrybut zawiera zwykłą liczbę; do niej należy dodać tę jednostkę”. Trzecim jest raw-string, który jest również domyślnym rozwiązaniem, gdy nie podano typu, i odtwarza dawne zachowanie.
/* 2. unit shorthand — the attribute holds a bare number */
width: attr(data-w px); /* data-w="10" → 10px */
rotate: attr(data-deg deg); /* data-deg="45" → 45deg */
animation-delay: attr(data-i s); /* data-i="2" → 2s */
width: attr(data-fill %); /* data-fill="87" → 87% *//* 3. raw-string — the default, and the old behaviour */
content: attr(data-name raw-string, "stranger");
content: attr(data-name); /* identical */
Skrót jednostki wymaga liczby bez jednostki
attr(data-w px) oznacza „przeczytaj liczbę z data-w i traktuj ją jako piksele”. Atrybut musi więc mieć postać data-w="10". Zapis data-w="10px" nie jest poprawny, ponieważ jednostka jest określana w arkuszu stylów, a nie w kodzie. Jeśli chcesz, aby HTML sam określał jednostkę (na przykład dlatego, że edytor może wpisać 2rem lub 10px), użyj type(<length>) i przechowaj pełną wartość.
Nieprawidłowy plik awaryjny powoduje błąd wcześniej i w inny sposób
Wartość awaryjna jest analizowana razem z resztą deklaracji, zanim przeglądarka w ogóle sprawdzi element. Jeśli napiszesz attr(data-w px, banana), deklaracja jest nieważna w momencie analizy i zostaje całkowicie odrzucona: atrybut nigdy nie jest odczytywany, a zamiast tego stosuje się wcześniejszą deklarację tej samej właściwości w regule. Jest to zupełnie inna awaria niż brak atrybutu, który zostaje odkryty dopiero znacznie później podczas obliczania wartości. Rozróżnianie tych dwóch przypadków w umyśle znacznie ułatwia debugowanie.
Rodzaje, które można deklarować
Wewnątrz type() akceptowane są następujące rodzaje, wraz z tym, co powinien zawierać atrybut oraz typowymi miejscami jego użycia.
<length>: wartości takie jak"12px"lub"2rem", przeznaczone dlawidth,paddinglubinset.
<percentage>: "87%", dla width lub translate.<length-percentage>: jeden z dwóch poprzednich, dla wszystkiego, co wymaga wartości rozmiaru.<number>: "4.5", dla opacity, scale lub jako dane wejściowe dla calc().<integer>: "3", dla grid-column, z-index lub order.<color>: "#4fc3f7" lub "tomato", dla color i background-color.<angle>: "45deg" lub "0.25turn", dla rotate lub kątów gradientu.<time>: "300ms", przeznaczone dla animation-delay i transition-duration.<resolution>: gęstość w formie "2dppx", używana przez image-resolution.<custom-ident>: identyfikator taki jak "card-3", przydatny do nadawania nazw elementom takim jak view-transition-name lub anchor-name.<string>: zapisana w cudzysłowach ciąg znaków CSS, zazwyczaj używana przez content.<transform-function>: coś w rodzaju "rotate(20deg)", stosowane z właściwością transform.<image> i <url>: obecne w gramatyce, ale faktycznie niewykorzystywane z powodów opisanych w sekcji dotyczącej bezpieczeństwa poniżej.*: przyjmuje wszystko i jest przede wszystkim przydatny do wykrywania funkcji.Liczby wymagają funkcji calc(), aby stać się wartościami rozmiarowymi
<number> nie ma wymiarów. 4.5 sam w sobie nie może ustalić szerokości, więc prawie zawsze znajduje się wewnątrz calc(). Najprostszym rozwiązaniem jest najpierw przechowanie tej wartości w specjalnej właściwości i wykonywanie tam obliczeń.
.stars {
--r: attr(data-rate type(<number>), 0);
width: calc(var(--r) * 20%); /* 4.5 → 90% */
}
Przejściowy parametr --r to coś więcej niż tylko formalność. Umożliwia ponowne użycie tej wartości w kilku deklaracjach, a ponieważ właściwości niestandardowe są dziedziczone, to właśnie w ten sposób wartość dociera do potomnych elementów – co, jak zobaczysz, jest jedynym sposobem na udostępnianie atrybutu elementom potomnym. Jeśli już używasz właściwości niestandardowych do przekazywania danych pomiędzy skryptami a stylami, artykuł na temat właściwości niestandardowych w czasie wykonywania omawia drugą stronę tego „mostu”.
Jak odbywa się wyszukiwanie: cztery możliwe wyniki, a nie dwa
Lekko jest założyć, że ta funkcja albo działa, albo reguła zostaje zignorowana. W rzeczywistości istnieją cztery różne wyniki, a ich pomieszanie jest najczęstszą przyczyną nieoczekiwanych sytuacji.
- Atrybut jest nieobecny. Jeśli podano alternatywę, zostanie ona użyta. W przeciwnym razie wynikiem jest gwarantowane wartość nieważna, a deklaracja staje się nieważna w momencie obliczenia wartości (IACVT).
- Atrybut istnieje, ale jest pusty (
data-x=""). Przy użyciu typizowanegoattr()pusta strona nie jest interpretowana jako ten typ, więc stosuje się alternatywę. Natomiast nietypizowana próba dostępu jest uznawana za sukces, który daje pustą stronicę, więc alternatywa jest pomijana. - Wartość nie może zostać zinterpretowana jako deklarowany typ. Jeśli istnieje alternatywa, zostanie ona użyta; w przeciwnym razie następuje IACVT.
- Wartość może zostać zinterpretowana. Otrzymuje się ją, a styl aktualizuje się natychmiast po zmianie atrybutu.
Co naprawdę oznacza „nieważne w momencie obliczenia wartości”
Błąd składniowy zostaje wykryty podczas analizy. Przeglądarka odrzuca tę deklarację, a wejściowa deklaracja tej samej właściwości w tej samej zasadzie staje się obowiązująca. Błąd IACVT występuje znacznie później, po tym jak mechanizm kaskadowy już wybrał odpowiednią deklarację, więc nie ma czego można by użyć jako alternatywy. Właściwość zachowuje się tak, jakby była ustawiona na unset: właściwości odziedziczone przyjmują wartość z elementu nadrzędnego, a właściwości nieodziedziczone wracają do swojej początkowej wartości.
.bar {
width: 50%; /* cascade drops this: the next one parsed fine */
width: attr(data-fill %); /* attribute missing → IACVT → width: auto */
}
/* result: auto. NOT 50%. */
Dlatego też zawsze dostarczanie alternatywy nie jest kwestią gustu. Bez niej błędnie napisana nazwa atrybutu po cichu przyjmuje wartość auto, 0 lub kolor odziedziczony, a konsola pozostaje cicha.
Dlaczego nieztypowana funkcja attr() nie może wygenerować koloru
Standardowy wybór raw-string wymaga szczególnej uwagi. Załóżmy, że reguła ustala wartość background-color na podstawie atrybutu bez określenia typu, a sprawdzamy to na elementach, które różnią się jedynie tym atrybutem. Element bez tego atrybutu otrzymuje wartość domyślną, zgodnie z oczekiwaniami. Każdy element, który ma ten atrybut, wyświetla się jako przezroczysty, włącznie z tym, który zawiera całkowicie poprawną nazwę koloru.
Wyjaśnienie brzmi tak: funkcja attr() bez określenia typu podstawia dosłowny tekst atrybutu jako ciąg znaków CSS, a nie przetworzoną wartość CSS. Atrybut background-color nie przyjmuje ciągu znaków, więc podstawienie zawodzi już na etapie obliczania wartości. Rozwiązanie awaryjne nie pomaga – obejmuje jedynie brakujący atrybut lub błąd przetwarzania zgodnie z deklarowanym typem, a w tym regule nic nie zostało zadeklarowane. Wyszukiwanie udało się pod kątem zwrotu ciągu znaków; później zawiodło przy próbie określenia koloru, gdy nie ma nic, co mogłoby to złapać.
/* data-c="red" — and the element still renders transparent */
.swatch { background-color: attr(data-c, gold); }
Wniosek jest prosty: zawsze, gdy właściwość docelowa to nie content, należy zadeklarować typ.
Wzory zastępujące skrypty
Różne operacje, które zespoły regularnie wykonywają za pomocą kilku linijek JavaScript, mogą teraz być realizowane wyłącznie w pliku stylów.
Szerokość z atrybutu data
W markup znajduje się zwykła liczba.
<div class="bar" data-fill="87"></div>
Skrypt stylów odczytuje to za pomocą skrótu procentowego i używa w razie błędu pustej kreski.
.bar {
height: 8px;
border-radius: 99px;
background: linear-gradient(90deg, #4fc3f7, #2dd4bf);
width: attr(data-fill %, 0%);
}
Kolor wybrany w CMS
.chip { background-color: attr(data-brand type(<color>), #888); }
Kolor marki wybrany przez redaktora treści to dane, a nie element projektu. Dzięki użyciu funkcji attr() można bezpośrednio sterować skryptem stylów, unikając konieczności dodawania stylów wewnątrz szablonu. Ponieważ walidacja <color> odbywa się w przeglądarce, błąd pisowni przez redaktora skutkuje po prostu użyciem szarej kreski zamiast uszkodzonego CSS.
Animacje listy z opóźnieniami
Opóźnione pojawianie się elementów zazwyczaj realizuje się poprzez ustawienie stylu wewnątrz pętli renderowania. Indeks jest jednak już częścią struktury markup i nie musi być przekształcany w właściwość stylu.
<li data-i="0">Inbox</li>
<li data-i="1">Drafts</li>
<li data-i="2">Sent</li>
Każdy element odczytuje swój własny indeks jako liczbę, a funkcja calc() przekształca go na opóźnienie.
li {
--i: attr(data-i type(<number>), 0);
animation: fade-in .45s both;
animation-delay: calc(var(--i) * .09s);
}
Jeśli atrybut już zawiera pełną wartość czasową, skrót jednostki s pozwala na jej bezpośrednie odczytanie.
/* or, if the attribute already holds a duration */
li { animation-delay: attr(data-delay s, 0s); }
Przekierowanie indeksu za pomocą --i ma dodatkową zaletę: krok czasowy staje się jedną konstantą CSS. Zmiana wartości .09s w jednym miejscu sprawia, że wszystkie listy z rozłożonym układem na stronie przyjmują nowy rytm.
Jedyny identyfikator view-transition-name na każdą kartę
To rozwiązanie umożliwia coś, co wcześniej nie było możliwe. Każdy element biorący udział w przejściu widoku musi mieć swój unikalny identyfikator view-transition-name; wcześniej oznaczało to konieczność tworzenia osobnej reguły dla każdej karty lub przypisywania nazw z skryptu. Teraz jedna reguła obsługuje wszystkie karty.
.card {
view-transition-name: attr(id type(<custom-ident>), none);
view-transition-class: card;
}
Należy pamiętać, że atrybutem w tym przypadku jest id, a nie atrybut data-*. Funkcja attr() może odczytać dowolny atrybut elementu, w tym id, href, title, colspan lub lang. Przestrzeń nazw data-* służy po prostu do przechowywania niestandardowych danych strony zgodnie z oczekiwaniami HTML. Połączenie tego nazwy z view-transition-class umożliwia stylizację przejść wszystkich karty za pomocą jednego selektora, mimo że każda z nich ma unikalną nazwę.
Praktycznym sposobem na sprawdzenie tych wzorców jest tymczasowe usunięcie bazowego kodu opartego na skryptach i sprawdzenie, czy stylizacja nadal jest widoczna; jeśli tak, to attr() pełni tę funkcję.
Pseudo-elementy: przypadek bez rozwiązania
Każda inna korzyść opisana w tym przewodniku ma alternatywę: zawsze można ustawić własną właściwość bezpośrednio w elemencie. Pseudo-elementy są inne – element.style odnosi się do samego elementu, a ::before lub ::after nie są węzłami DOM, więc nie ma czego przymocować do nich stylu bezpośredniego. Poprzednie opcje to własna właściwość odziedziczona od elementu host (która nadal wymaga jakiegoś stylizowania hosta) lub utworzony plik stylów. Natomiast atrybut w elemencie host może być odczytywany bezpośrednio z obu pseudo-elementów.
Oto miernik, którego jedynymi danymi jest pojedynczy atrybut.
<div class="meter" data-pct="72"></div>
Etykieta używa klasycznej funkcji ciągu znaków attr() w polu content, która działa we wszystkich przeglądarkach.
.meter::before {
content: attr(data-pct) "% complete";
}
Pas wykorzystuje zaimplementowany format do ustawienia swojej szerokości na podstawie tego samego atrybutu.
.meter::after {
content: "";
display: block;
height: 10px;
width: attr(data-pct %, 0%);
background: linear-gradient(90deg, #fbbf24, #f472b6);
}
Jeden atrybut jest używany przez dwa pseudo-elementy, a żaden z nich nie wymaga dodatkowego elementu potomnego. Odliczanie odznak, pierścieni postępu, tekstu w podpowiedziach, przedrostków porządkowych, sufiksów jednostek oraz etykiet danych wykresu – wszystko to ma tę samą strukturę: pseudo-element, który potrzebuje liczby, którą element gospodarz już posiada. Przed wprowadzeniem funkcji attr() tę liczbę trzeba było wpisywać dwukrotnie – raz jako atrybut dla tekstu, a raz jako własność niestandardową dla elementów geometrycznych – przy czym te dwie wersje mogły się różnić. Teraz wystarczy ją wpisać raz.
funcja attr() czyta tylko element, który jest stylizowany
Funkcja ta odczytuje atrybuty z elementu, do którego odnosi się reguła stylizacji. Nie sprawdza elementów przodków ani rodzeństwa, a nie istnieje odpowiednik funkcji closest(). Ponieważ w razie błędu stosowany jest mechanizm awaryjny, taki błąd przebiega bez żadnych objawów, dlatego niemal każdy popełnia go przynajmniej raz.
Rozważmy sekcję, która określa kolor akcentowy dla swojego zawartości.
<section class="theme" data-accent="#4fc3f7">
<h2>Title</h2>
</section>
Czytanie tego atrybutu z nagłówka nie przynosi żadnej korzyści, ponieważ nagłówek go nie posiada.
/* WRONG — this reads the h2, which has no data-accent */
.theme h2 { color: attr(data-accent type(<color>), #888); }
/* every heading is #888, forever, with no error */
Rozwiązaniem jest odczytanie atrybutu raz, w elemencie, który go posiada, oraz przekazanie jego wartości za pomocą własności dostosowanej.
/* RIGHT — read it once on the element that has it, then inherit */
.theme { --accent: attr(data-accent type(<color>), #888); }
.theme h2 { color: var(--accent); }
.theme a { border-bottom: 2px solid var(--accent); }
Dziedziczenie odpowiada za rozpowszechnianie tej wartości. Jako dodatek każdy poddrzewo może teraz przejąć kontrolę nad kolorem akcentowym za pomocą zwykłej deklaracji --accent, co metoda bezpośredniego użycia attr() nigdy nie mogłaby umożliwić.
Dlaczego adresy URL są niedozwolone
Każda wartość wygenerowana przez attr() jest oznaczona jako attr()-tainted, a taka wartość nigdy nie może być użyta do tworzenia adresu URL, bez względu na ścieżkę. Obie te deklaracje nie mają żadnego efektu:
span[data-icon] { background-image: url(attr(data-icon)); }
span[data-icon] { background: image-set(attr(data-icon)); }
Zanieczyszczenie przemieszcza się wraz z wartością. Przekierowywanie go za pomocą własnej właściwości, owijanie go w src() lub image(), czy też umieszczanie go wewnątrz innych funkcji na dowolnej głębokości pozostawia ślad w tym samym miejscu. Gdziekolwiek trafi ono do kontekstu URL, deklaracja staje się nieważna w momencie obliczania wartości.
Model zagrożeń wyjaśnia tę zasadę. Wartości atrybutów są często wpływane przez użytkowników: nazwy wyświetlania, identyfikatory CMS oraz tytuły komentarzy. Jeśli plik stylów mógłby przekształcić taką wartość w żądanie sieciowe, każda osoba mogąca edytować to pole mogłaby sprawić, że przeglądarka każdego odwiedzającego pobrałaby adres według jej wyboru, wraz z wszystkim, co przeglądarka zwykle dołącza do takich żądań. Rozwiązanie tego problemu w specyfikacji umożliwiło producentom przeglądarek uzgodnienie wdrożenia tej funkcji. Ogłoszenie WebKit na Interop 2026 jasno to pokazuje: po ustaleniu kwestii bezpieczeństwa w specyfikacji wszystkie silniki oświadczyły, że są gotowe do wdrożenia tej możliwości i to w sposób interoperacyjny.
Pamiętaj o ograniczeniach tej ochrony. Wszystko, co znajduje się w atrybucie, jest dostępne dla każdego skryptu na stronie, widoczne w widoku źródłowym i przekazywane w treści HTML. Zabranienie konkretnej URL zamienia jedną z dróg ucieczki danych, ale nie czyni atrybutów prywatnymi. Nigdy nie umieszczaj w nich tajemnic.
Detekcja obsługi i stopniowe wdrażanie
Bezpiecznym podejściem jest użycie prostego standardu zrozumiałego przez wszystkie przeglądarki, z dodaniem wersji zapisanej specjalnie dla silników, które ją obsługują. Zacznij od standardu, w tym przypadku realizowanego za pomocą własnej właściwości wewnątrz elementu.
.bar {
width: var(--fill, 0%); /* baseline, everyone */
}
Następnie niech przeglądarki, które to obsługują, przejdą na użycie tego atrybutu.
@supports (width: attr(x %)) {
.bar {
width: attr(data-fill %, 0%); /* modern browsers take over */
}
}
Ten sam test jest dostępny w czasie wykonywania, jeśli twój kod wymaga podziału na różne ścieżki.
// the same answer at runtime, if you need to branch in code
if (CSS.supports("width", "attr(x %)")) {
// typed attr() is available
}
Testowanie na rzeczywistej właściwości
Należy uważać na fragmenty służące do wykrywania, które używają nazwy właściwości zastępczej, takiej jak @supports (x: attr(x type(*))), pojawiającej się w dokumentacji referencyjnej. Nie istnieje żadna właściwość CSS o nazwie x, a każdy przeglądarka zwraca wartość false, gdy zapytanie o obsługę odnosi się do właściwości, której nie zna, więc test zawodzi nawet w przeglądarkach, w których dana funkcja działa. Należy używać istniejącej właściwości. Zapytanie @supports (width: attr(x %)) jest niezawodne: width to rzeczywista właściwość, a attr(x %) może być rozszyfrowane jedynie przez silniki, które implementują skrót dla jednostek. Warto sprawdzić aktualną dokumentację MDN dotyczącą funkcji attr(), ponieważ przykłady w dokumentacji rzeczywiście są korygowane.
Opieranie się na zasadzie kaskady zamiast @supports
Krótsza wersja pomija blok @supports i pozwala zasadzie kaskady podjąć decyzję.
.bar {
width: var(--fill, 0%); /* everyone */
width: attr(data-fill %, 0%); /* supporting browsers override */
}
Przeglądarka, która nie potrafi zinterpretować wyrażenia attr() wewnątrz atrybutu width, odrzuca taką deklarację podczas analizy, dzięki czemu obowiązuje linijka zapisana wcześniej. Takie zachowanie wynika z sposobu, w jaki mechanizm kaskadowy radzi sobie z nieważnymi deklaracjami. Mimo to forma @supports wyraźnie określa intencję i wymaga jedynie kilku dodatkowych linijek, więc należy ją preferować, jeśli chce się, aby zachowanie było oczywiste dla przyszłych czytelników; warto również sprawdzić krótszą formę w przeglądarce, która jej nie obsługuje, zanim się na nią polegnie.
We wszystkich wariantach należy zachować opcję awaryjną bezpośrednio wewnątrz attr(). W przeglądarce, która dany atrybut obsługuje, brakujący atrybut nie przechodzi na deklarację var() umieszczoną powyżej; deklaracja attr() już wygrała w procesie kaskadowym, więc staje się IACVT, a wartość szerokości ustala się na auto.
Obsługa przeglądarek w momencie pisania
Wsparcie zmienia się szybko, więc traktuj te dane jako chwilowy obraz sytuacji i potwierdź je na stronie Can I use przed poleganiem na nich. Od sierpnia 2026 roku około 70 procent użytkowników na świecie miało przeglądarkę z obsługą funkcji attr():
- Chrome 133 i nowsze: obsługiwane, pierwsze wersje je wprowadziły (zobacz ogłoszenie Chrome for Developers).
- Edge 133 i nowsze: obsługiwane, idą w parze z wersją Chromium.
- Firefox 155 i nowsze: obsługiwane; obecne, ale domyślnie wyłączone w wersjach od 152 do 154.
- Samsung Internet 29 i nowsze: obsługiwane.
- Safari na komputerach stacjonarnych: jeszcze nie dostępne; dostępne tylko w wersji Technology Preview.
- Safari na iOS: jeszcze nie dostępne, a ponieważ wszystkie przeglądarki iOS używają WebKit, obejmuje to wszystkie przeglądarki na tym systemie.
Klasyczna funkcja attr() obsługująca tylko łańcuchy tekstowe w elementach content to odrębna kwestia. Była powszechnie dostępna od dekady, już w najwcześniejszych wersjach wszystkich głównych silników. Jeśli wystarczy Ci content: attr(data-label), możesz ją używać bez konieczności stosowania powyższych środków ostrożności.
Interop 2026 wymienia typowaną funkcję attr() wśród swoich kluczowych obszarów działania. Interop to coroczna inicjatywa współpracy, w której Apple, Google, Microsoft i Mozilla, wraz z Igalia oraz Bocoup, wybierają funkcje, które brakuje lub nie działają prawidłowo w co najmniej jednym silniku, i zobowiązują się je ulepszyć w ciągu roku. Postępy są monitorowane na publicznej tablicy kontrolnej opartej na wspólnych testach Web Platform Tests. To najsilniejszy publiczny sygnał, że rozwijana jest obsługa Safari, ale jest to zobowiązanie, a nie data wydania. Planujcie rozwiązanie awaryjne na resztę 2026 roku. Aby zapoznać się z szerszym podejściem do wdrażania nowego CSS zgodnie z bezpiecznymi standardami, sprawdźcie wdrażanie nowoczesnego CSS w sposób bezpieczny.
Wybór między attr() a JavaScriptem
Żaden z tych narzędzi nie jest lepszy we wszystkich przypadkach. Poniższy podział służy jako praktyczny przewodnik.
Należy preferować attr(), gdy:
- Markup już zawiera tę wartość. Przenoszenie jej do skryptu tylko po to, by ponownie ją ustawić w tym samym węźle, nic nie daje.
- Musi znajdować się bezpośrednio w pierwszym elemencie. Atrybut istnieje od razu po utworzeniu elementu przez parser, podczas gdy skrypt jest wykonywany później i może spowodować widoczną korektę.
- Pseudo-element potrzebuje tej wartości. Stylizacje wstawione bezpośrednio za pomocą
element.stylenie mogą dotrzeć do::beforeani::after. - Wdrażasz ścisłą politykę bezpieczeństwa treści. Stylizacje wstawione bezpośrednio wymagają użycia
unsafe-inlinelub wartości nonce; atrybuty danych nie wymagają żadnego z nich.
MutationObserver do monitorowania drzewa.Zostań przy JavaScript, gdy:
- Wartość nie ma powodu, by istnieć w DOM. Wymiary uzyskane z
ResizeObserverlub obsługi przewijania nie powinny najpierw być przekształcane w atrybut. - Zaktualizowuje każdą klatkę animacji. Wywołanie
el.style.setProperty('--x', v)jest bardziej bezpośrednie i pozwala uniknąć konieczności definiowania atrybutu. - Dane przekazywane są duże, zagnieżdżone lub poufne. Wszystko, co znajduje się w atrybucie, jest ściśle widoczne dla publiczności.
- Dziś potrzebujesz Safari, a wartość ma znaczenie. Pasek postępu zatrzymany na zerze w jednym przeglądarce to wada, a nie eleganckie rozwiązanie awaryjne.
W pierwszej liście zupełnie nie ma mowy o wydajności. Ustawienie jednego stylu inline nigdy nie było wąskim gardłem. Zyskujesz lepsze przechowywanie wartości oraz lepszą możliwość jej widzenia, a nie szybszą pracę.
Lista potencjalnych problemów
- Zawsze podawaj rozwiązanie awaryjne. Brakująca lub nieprzetwarzalna wartość bez takiego rozwiązania powoduje błąd IACVT oraz właściwość
unset, a nie wcześniejszą deklarację. - Pusty atrybut to nie atrybut brakujący. Atrybut niesprecyzowany typu,
data-x="", zwraca pustą ścieżkę, więc mechanizm awaryjny nigdy nie zostaje uruchomiony. - Niesprecyzowany typ oznacza tekst dosłowny.
background-color: attr(data-c, gold)przy użyciudata-c="red"nie wyświetla nic; należy określić typ atrybutu. - Nie ma funkcji
url()aniimage-set(). To celowe rozwiązanie, które przetrwa nawet przy użyciu własnych właściwości. - Czytany jest tylko element, który pasuje do reguły. W przypadku przodków wartość należy przechować w własnej właściwości, aby mogła zostać odziedziczona.
- Dla skrótów jednostek potrzebne są same liczby. Należy używać
data-w="10", a niedata-w="10px". - Nieważny mechanizm awaryjny powoduje przerwanie deklaracji już podczas analizy, co stanowi wcześniejszy i inny błąd niż brakujący atrybut.
- A
<number>sam w sobie nie może określić rozmiaru niczego. Zapisz go i pomnóż w funkcjicalc(). - Atrybuty są publiczne. Unikaj przechowywania w nich tajemnic.
- Przetestuj funkcjonalność z rzeczywistą właściwością, na przykład
@supports (width: attr(x %)). - Safari jeszcze tego nie wdrożył w momencie pisania, więc zachowaj bazę dla wszystkiego istotnego.
Podsumowanie
Typowanie funkcji attr() polega na niewielkiej zmianie składni, która ma wyraźny wpływ na architekturę: wartości już obecne w HTML nie wymagają już skryptu, aby stać się stylami. Ta funkcjonalność sprzyja precyzji – należy określić typ, zawsze zapewnić wartość awaryjną, odczytać atrybut z elementu, który go posiada, i przekazać go dalej za pomocą własnych właściwości, przyjmując jednocześnie, że adresy URL są trwale niedostępne. Każde użycie tej funkcji powinno być połączone z wartością bazową do czasu wydania wersji Safari; taki model może zostać wprowadzony do produkcji już dziś, a arkusz stylów przejmie kontrolę automatycznie w miarę rozprzestrzeniania się obsługi. Formalna definicja znajduje się w projekcie CSS Values and Units Level 5, jeśli potrzebujesz dokładnych zasad parsowania.
Literatura pokrewna
- Kto ma kontrolować behawior interfejsu? Wybór między HTML, CSS a JavaScript — Framework do decydowania, kiedy wbudowane funkcje HTML i CSS, takie jak dialogi, popovery, :has() oraz zapytania kontenerów, powinny zastąpić JavaScript, a kiedy nie.
- Bezpieczne wdrożenie nowoczesnego CSS: Anchory, Grid Lanes, Scope i light-dark() — Dowiedz się, jak pozycjonowanie anchorów, popovery, układ w stylu grid-lanes, przejścia między widokami, funkcje light-dark() oraz @scope zastępują biblioteki JavaScript, oraz jak wdrożyć każdą z nich z rozwiązaniami awaryjnymi.