Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds
Powrót do kolekcji
Przewodnik11 min czytania

Radix UI, komponenty bez stylów

Radix daje nieostylowane komponenty React z dostępnością w standardzie. Jak działa kompozycja, stan projektu po przejęciu i co oznacza pojawienie się Base UI.

Radix UI, czyli zachowanie bez wyglądu

Radix rozwiązuje problem, który wygląda na drobny, a pochłania zaskakująco dużo czasu: napisanie okna dialogowego, listy rozwijanej albo podpowiedzi tak, żeby działały poprawnie dla wszystkich użytkowników.

Biblioteka dostarcza komponenty pozbawione stylów, za to z pełnym zachowaniem: obsługą klawiatury, atrybutami dla czytników ekranu, pułapką ogniskowania wewnątrz okna, zamykaniem klawiszem wyjścia i poprawnym umieszczaniem elementów unoszących się nad treścią. Wygląd dokładasz sam, dowolnym sposobem.

Ten podział okazał się na tyle trafny, że biblioteka stała się fundamentem dla wielu zestawów komponentów, w tym najpopularniejszego dziś rozwiązania opartego o kopiowanie kodu do własnego projektu.

Stan projektu w połowie 2026 roku

Zacznijmy od rzeczy, która przy tej bibliotece ma teraz największe znaczenie praktyczne, a której starsze materiały nie mówią.

Radix nie jest wycofany i nadal działa. Projekt trafił jednak pod skrzydła innej firmy, a tempo wydań wyraźnie zwolniło, zwłaszcza przy bardziej złożonych komponentach.

Równolegle powstał Base UI, tworzony przez część tych samych osób, które budowały Radix, przy udziale zespołów stojących za innymi znanymi bibliotekami. Wydania wychodzą tam co miesiąc, a nad projektem pracuje zespół na pełen etat.

W lipcu 2026 roku najpopularniejszy zestaw komponentów oparty na tej warstwie przełączył domyślny wybór dla nowych projektów właśnie na Base UI, przy czym Radix pozostaje w pełni wspierany i nowe komponenty trafiają do obu wariantów.

Wniosek praktyczny jest dwuczęściowy. Projekt działający na Radiksie nie wymaga pilnej migracji, bo biblioteka jest stabilna i nadal utrzymywana. Nowy projekt warto natomiast zacząć od świadomego wyboru między dwiema opcjami, zamiast przyjmować Radix jako oczywistość.

Kompozycja zamiast konfiguracji

Sposób budowania komponentów jest tu wyróżnikiem i warto go zrozumieć, bo przenosi się na obie biblioteki.

Code
TypeScript
import { Dialog } from 'radix-ui'

export function OknoPotwierdzenia({ children }) {
  return (
    <Dialog.Root>
      <Dialog.Trigger asChild>
        <button className="px-4 py-2 bg-red-600 text-white rounded">
          Usuń konto
        </button>
      </Dialog.Trigger>
      <Dialog.Portal>
        <Dialog.Overlay className="fixed inset-0 bg-black/50" />
        <Dialog.Content className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-white p-6 rounded-lg">
          <Dialog.Title className="text-lg font-semibold">
            Na pewno?
          </Dialog.Title>
          <Dialog.Description className="text-sm text-gray-600">
            Tej operacji nie da się cofnąć.
          </Dialog.Description>
          {children}
          <Dialog.Close asChild>
            <button>Anuluj</button>
          </Dialog.Close>
        </Dialog.Content>
      </Dialog.Portal>
    </Dialog.Root>
  )
}

Komponent nie jest jednym elementem z dwudziestoma właściwościami, tylko zestawem części, które składasz sam. To znaczy, że możesz zmienić układ, dołożyć własne elementy w środku i ostylować każdą część osobno, bez walki z gotowym szablonem.

Właściwość pozwalająca podstawić własny element zamiast domyślnego jest tu najważniejszym mechanizmem. Dzięki niej przycisk otwierający okno to Twój przycisk, z Twoimi klasami i Twoim kodem, a biblioteka dokłada mu tylko zachowanie i atrybuty dostępności.

Stan komponentu jest wystawiany jako atrybuty w kodzie strony, więc stylowanie stanów odbywa się przez zwykłe selektory, bez trzymania tego w stanie aplikacji.

Code
CSS
[data-state='open'] { animation: pojaw 150ms ease-out; }
[data-state='closed'] { animation: znikaj 100ms ease-in; }

Co dostajesz w zamian za brak stylów

Warto wyliczyć konkretnie, bo z zewnątrz wygląda to na „to samo, co napisałbym sam", a różnica leży w szczegółach, o których łatwo zapomnieć.

Obsługa klawiatury zgodna z tym, czego użytkownicy oczekują: strzałki w listach, klawisz wyjścia zamykający okno, tabulacja poruszająca się wyłącznie wewnątrz otwartego okna dialogowego, klawisz spacji i enter działające tam, gdzie powinny.

Atrybuty dla technologii wspomagających, w tym powiązania między etykietą, opisem i kontrolką oraz informowanie o zmianie stanu. To jest część, którą najłatwiej zrobić źle, bo błąd nie objawia się niczym widocznym.

Zwracanie ogniskowania do elementu, który otworzył okno, po jego zamknięciu. Drobiazg, którego brak zauważa wyłącznie osoba pracująca z klawiaturą, i wtedy zauważa go boleśnie.

Umieszczanie elementów unoszących się nad treścią z uwzględnieniem krawędzi okna przeglądarki, przewijania i przestrzeni dostępnej po każdej stronie. Napisanie tego samodzielnie wygląda na godzinę pracy i zajmuje tydzień.

Obsługa przypadków, o których się nie myśli: kliknięcie poza obszarem, wiele okien jeden nad drugim, blokada przewijania strony pod otwartym oknem, zachowanie na urządzeniach dotykowych.

Cztery części projektu

Nazwa bywa myląca, bo obejmuje kilka rzeczy o różnym przeznaczeniu, a materiały w sieci mieszają je ze sobą.

Warstwa podstawowa to opisane wyżej komponenty bez stylów. To jest część, o której mówi się najczęściej i którą wykorzystują inne biblioteki.

Osobno istnieje zestaw komponentów ze stylami, gotowy do użycia bez pisania własnego wyglądu. To odpowiednik kompletnych bibliotek komponentów i nie ma nic wspólnego z filozofią braku stylów, poza wspólnym pochodzeniem.

Trzecia część to system kolorów: zestaw skal zaprojektowanych tak, żeby odcienie o tym samym numerze pełniły tę samą rolę niezależnie od barwy. Można go używać zupełnie osobno, również w projekcie, który z resztą nie ma nic wspólnego, i to jest jego najczęstsze zastosowanie.

Czwarta to zestaw ikon w jednolitym stylu, również niezależny od pozostałych i możliwy do użycia samodzielnie.

Rozdzielenie tych czterech rzeczy przy szukaniu dokumentacji oszczędza sporo zamieszania, bo poradnik o komponentach ze stylami nie pomoże przy pracy z warstwą podstawową i odwrotnie.

Elementy unoszące się nad treścią

Warto poświęcić chwilę jednej kategorii komponentów, bo to tam różnica między biblioteką a własną implementacją jest największa.

Lista rozwijana, podpowiedź i menu kontekstowe muszą pojawić się w miejscu, w którym się mieszczą. Jeśli przycisk stoi przy dolnej krawędzi okna, menu ma rozwinąć się w górę. Jeśli przy prawej, ma przesunąć się w lewo. Jeśli strona się przewija, element ma podążać za elementem, do którego należy, albo zniknąć.

Do tego dochodzi umieszczenie w strukturze strony. Element renderowany wewnątrz kontenera z ukrytym przepełnieniem zostanie przycięty, więc biblioteka przenosi go na koniec dokumentu i utrzymuje powiązanie logiczne mimo fizycznego oddalenia.

Do tej samej kategorii należy szuflada wysuwana od krawędzi ekranu, której Radix nie ma we własnym zestawie. Najczęściej sięga się po Vaul, zbudowany na tutejszym oknie modalnym i dokładający gesty oraz punkty zatrzymania. Zanim go dodasz, sprawdź stan utrzymania projektu, bo autor ogłosił, że go nie rozwija.

Code
TypeScript
<Popover.Portal>
  <Popover.Content
    side="bottom"
    align="start"
    sideOffset={8}
    collisionPadding={16}
    className="rounded-lg border bg-white p-4 shadow-lg"
  >
    <Popover.Arrow className="fill-white" />
    {tresc}
  </Popover.Content>
</Popover.Portal>

To rozwiązuje problem, który przy własnej implementacji objawia się jako menu ucięte w połowie i naprawiany bywa ustawianiem coraz wyższych wartości warstwy. Wskazana strona i wyrównanie są tu preferencją, a nie nakazem: przy braku miejsca element sam przeskoczy na drugą stronę, a odstęp od krawędzi z ostatniej właściwości pilnuje, żeby nie przykleił się do brzegu okna.

Trzecia warstwa to zachowanie przy klawiaturze i czytniku ekranu: powiązanie elementu otwierającego z otwartym, informacja o rozwinięciu, poruszanie się strzałkami z zawijaniem na końcach listy, zamykanie i zwracanie ogniskowania.

Suma tych rzeczy sprawia, że napisanie poprawnego menu rozwijanego to zadanie na kilka dni, a nie na popołudnie. To jest właśnie wartość, za którą sięga się po tę bibliotekę, i powód, dla którego nawet zespoły z własnym systemem projektowym budują go na tej warstwie zamiast od zera.

Radix kontra alternatywy

RozwiązanieMocna stronaSłabośćKiedy wybrać
Radix UIDojrzały, ogromna liczba wdrożeń, stabilnyWolniejsze tempo wydań, brak części nowszych komponentówProjekt już na nim oparty
Base UIAktywny rozwój, ci sami autorzy, nowsze komponentyKrótsza historia, mniej materiałów w sieciNowy projekt zaczynany dziś
shadcn/uiGotowe komponenty do skopiowania, styl w zestawieTo warstwa nad powyższymi, nie alternatywaSzybki start z gotowym wyglądem
MantineKompletny zestaw ze stylami, wiele gotowych elementówWłasny system stylów do przyswojeniaPanel administracyjny, aplikacja wewnętrzna

Trzeci wiersz wymaga wyjaśnienia, bo bywa mylony z pozostałymi. To nie jest konkurencyjna biblioteka, tylko zestaw gotowych komponentów zbudowanych na warstwie z pierwszych dwóch wierszy, kopiowanych do Twojego projektu razem z kodem i stylami. Wybierając go, wybierasz też jedną z warstw pod spodem.

Wybór między pierwszym a drugim wierszem sprowadza się do wieku projektu. Istniejący kod na Radiksie zostaw, bo biblioteka jest stabilna i wspierana. Nowy projekt zaczynaj od Base UI, bo tam idzie rozwój, tam pojawiają się brakujące komponenty i tam kieruje domyślny wybór najpopularniejszego zestawu.

Migracja i współistnienie

Jeśli rozważasz przejście, warto wiedzieć, jak duża to praca i czy w ogóle jest potrzebna.

Interfejsy obu bibliotek są podobne, bo wyrosły z tego samego myślenia, natomiast nie są identyczne. Nazwy części komponentów, sposób przekazywania stanu i część właściwości się różnią, więc migracja to przepisywanie komponent po komponencie, a nie podmiana importu.

Dobra wiadomość jest taka, że obie biblioteki mogą działać obok siebie w jednym projekcie. To pozwala przenosić kod stopniowo, zaczynając od komponentów, które i tak wymagają zmian, zamiast planować jedno wielkie przepisanie.

Kolejność, która sprawdza się w praktyce: najpierw komponenty najprostsze i najczęściej używane, bo przy nich nauczysz się różnic taniej. Potem te, w których brakuje Ci czegoś w obecnej wersji. Najbardziej rozbudowane zostaw na koniec, bo tam różnice bywają największe.

Nie migruj dlatego, że coś jest nowsze. Migruj wtedy, gdy potrzebujesz komponentu, którego w obecnej wersji po prostu nie ma, albo gdy trafiasz na błąd, który nie zostanie naprawiony.

Formularze i dostępność w praktyce

Biblioteka daje solidną podstawę, a i tak największą różnicę robi kilka rzeczy, które trzeba dołożyć samodzielnie.

Etykieta powiązana z kontrolką to podstawa, o której łatwo zapomnieć przy własnych komponentach. Sam znacznik wizualny nad polem nie wystarcza, bo czytnik ekranu nie odczyta go razem z polem, jeśli nie ma między nimi jawnego powiązania.

Code
TypeScript
<label htmlFor="email">Adres e-mail</label>
<input
  id="email"
  type="email"
  aria-describedby={blad ? 'email-blad' : undefined}
  aria-invalid={blad ? true : undefined}
/>
{blad && (
  <p id="email-blad" role="alert">
    {blad}
  </p>
)}

Komunikat błędu również musi być powiązany, i to inaczej niż etykieta. Bez tego użytkownik korzystający z czytnika dowiaduje się, że formularz nie przeszedł walidacji, ale nie wie, które pole zawiodło. Rola przypisana akapitowi w ostatniej linii sprawia dodatkowo, że komunikat jest ogłaszany w chwili pojawienia się, a nie dopiero po dojściu do niego kursorem czytnika.

Kolejność ogniskowania powinna odpowiadać kolejności wizualnej. Układ zmieniony stylami tak, że pole widoczne jako pierwsze jest w kodzie trzecie, działa poprawnie myszą i myląco klawiaturą.

Kontrast kolorów pozostaje w całości po Twojej stronie, bo biblioteka nie wnosi żadnych barw. Szary tekst na jaśniejszym szarym tle wygląda schludnie na makiecie i bywa nieczytelny na ekranie w słońcu.

Ostatnia rzecz to sprawdzenie tego wszystkiego w praktyce. Przejście przez formularz wyłącznie klawiaturą zajmuje dwie minuty i wyłapuje więcej problemów niż jakiekolwiek narzędzie automatyczne.

Code
JavaScript
document.addEventListener('focusin', (e) => {
  console.log(e.target.tagName, e.target.id || e.target.className)
})

Ten fragment wklejony do konsoli wypisuje każde przejęcie ogniskowania, więc widać wprost, czy kolejność odpowiada układowi na ekranie i czy po zamknięciu okna ogniskowanie wróciło tam, skąd wyszło. Widać też miejsca, w których znika zupełnie, a to jest ten przypadek, po którym dalsza nawigacja klawiaturą przestaje działać.

Typowe błędy

Pierwszy to zakładanie, że biblioteka załatwia dostępność w całości. Dostarcza zachowanie i atrybuty, natomiast kontrast kolorów, sensowne etykiety i kolejność ogniskowania w formularzu pozostają po Twojej stronie.

Drugi to pomijanie właściwości pozwalającej podstawić własny element. Bez niej powstaje zagnieżdżony przycisk wewnątrz przycisku, co jest nieprawidłowe i psuje zachowanie.

Trzeci to trzymanie się starych instrukcji instalacji. Dokumentacja każe dziś zainstalować jeden pakiet radix-ui i importować z niego części, a osobne paczki @radix-ui/react-* są ścieżką zastaną, opisaną w materiałach sprzed ujednolicenia. Jeśli zależy Ci na tym, żeby narzędzie budujące odcięło niepotrzebny kod, importuj z podścieżki, na przykład radix-ui/popover, zamiast wracać do osobnych paczek.

Czwarty to walka ze stylami domyślnymi, których nie ma. Jeśli coś wygląda źle, przyczyna leży w Twoim kodzie stylów, a nie w bibliotece, bo ona nie wnosi żadnego wyglądu.

Piąty to rozpoczynanie nowego projektu na Radiksie bez sprawdzenia stanu obu bibliotek. Rozwój idzie dziś w stronę Base UI i warto podjąć tę decyzję świadomie.

Szósty to planowanie migracji jako jednej dużej operacji. Obie biblioteki działają obok siebie w jednym projekcie, więc przenoszenie stopniowe jest tańsze, bezpieczniejsze i nie blokuje reszty prac.

FAQ

Czy Radix UI jest jeszcze rozwijany?

Tak, ale wolniej niż wcześniej. Projekt jest wspierany i stabilny, natomiast tempo wydań spadło, a część nowszych komponentów pojawia się najpierw gdzie indziej. Do istniejących projektów jest to nadal solidny wybór.

Czym jest Base UI wobec Radiksa?

Nowszą biblioteką tworzoną przez część tych samych osób, z aktywniejszym rozwojem i miesięcznymi wydaniami. Nie jest oficjalnym następcą, natomiast to tam trafiają dziś nowe komponenty i tam kieruje domyślny wybór najpopularniejszego zestawu gotowych komponentów.

Czym różni się od shadcn/ui?

To dwie różne warstwy. Radix daje zachowanie bez wyglądu, shadcn/ui to gotowe komponenty ze stylami zbudowane na tej warstwie i kopiowane do Twojego projektu. Używając drugiego, używasz też pierwszego albo jego nowszego odpowiednika.

Czy muszę używać konkretnego systemu stylów?

Nie. Biblioteka nie narzuca niczego, więc działa z klasami narzędziowymi, komponentami stylowanymi, zwykłymi arkuszami i modułami. Stan komponentów wystawiony jako atrybuty pozwala stylować stany zwykłymi selektorami.

Czy warto migrować istniejący projekt?

Nie z samego powodu, że coś jest nowsze. Warto wtedy, gdy potrzebujesz komponentu, którego w obecnej wersji nie ma, albo trafiasz na błąd bez perspektywy naprawy. Obie biblioteki działają obok siebie, więc przenoszenie może być stopniowe.

Dokumentacja stoi na stronie projektu, a kod w repozytorium na GitHubie.