Strona główna / Artykuły / Długość wiersza, skale odstępów, ciemne powierzchnie, cienie i pierścienie ostrości w CSS

Długość wiersza, skale odstępów, ciemne powierzchnie, cienie i pierścienie ostrości w CSS

Przyswoj podstawy CSS odpowiedzialne za eleganckie interfejsy: długość wierszy oparta na ch, skala odstępów 4px, warstwowe ciemne powierzchnie, wielowarstwowe cienie oraz pierścienie widoczne przy skupieniu uwagi.

1903 słów

Aplikacja internetowa może mieć modny gradient, błyszczące karty oraz płynne animacje, a mimo to już po kilku sekundach użytkowania sprawia wrażenie niedokończonej. Nic nie jest zepsute, jednak jej wygląd przypomina prototyp stworzony na weekend, a nie produkt na poziomie Linear, Stripe czy Vercel. Przyczyną rzadko jest paleta kolorów lub brak talentu artystycznego. Zazwyczaj chodzi o kilka mierzalnych decyzji związanych z CSS dotyczących długości linii, odstępów, kontrastu, wysokości elementów oraz stanów skupienia uwagi, a ten artykuł pokazuje, jak prawidłowo je podjąć.

Ogranicz długość linii za pomocą jednostek ch

Tekst rozciągający się na całą szerokość monitora o rozdzielczości 1440px to jeden z najwyraźniejszych oznak nieudoskonalonego interfejsu użytkownika. Bardzo długie wersety są trudne do odczytania – gdy liczba znaków w wersecie przekracza około 80, wzrok musi wracać aż na lewy brzeg, często lądując przy niewłaściwym wersecie. Na długiej stronie takie ciągłe szukanie właściwego miejsca jest męczące.

Zazwyczaj wygląda to w ten sposób: kontener o pełnej szerokości, bez żadnych ograniczeń dla akapitu:

{/* BAD: Unbounded text stretches across the whole viewport */}
<div className="w-full p-8">
  <h1 className="text-3xl font-bold">API Documentation</h1>
  <p className="text-slate-300 mt-4 text-base">
    Our platform enables developers to authenticate and stream webhook events in real-time... (stretches 1200px wide)
  </p>
</div>

Rozwiązanie: dostosowanie rozmiaru bloków tekstu według liczby znaków

CSS posiada jednostkę stworzoną specjalnie do rozwiązania tego problemu. ch ma taką samą szerokość jak znak „0” w danym czcionku, dzięki czemu użycie tej jednostki do określania rozmiaru tekstu pozwala kontrolować liczbę znaków na linii niezależnie od rozmiaru czcionki. Wygodna lektura zazwyczaj wymaga od 45 do 75 znaków na linii. Klasa przeznaczona do stylizacji tekstu może łączyć ograniczenie liczby znaków na linii oparte na ch z odpowiednio dużą wysokością linii oraz nieco mniejszą odległością między literami:

/* Clean readable prose container */
.prose-container {
  max-width: 68ch; /* Optimal line length regardless of font size */
  line-height: 1.65;
  letter-spacing: -0.01em;
}

W Tailwind utility max-w-prose zapewnia podobne ograniczenie (65ch), a mx-auto centruje kolumnę:

{/* Enterprise Grade: Beautiful, focused reading experience */}
<div className="max-w-prose mx-auto px-6 py-12">
  <h1 className="text-3xl font-bold tracking-tight text-white">
    API Documentation
  </h1>
  <p className="mt-4 text-slate-300 text-base leading-relaxed">
    Our platform enables developers to authenticate and stream webhook events in real-time...
  </p>
</div>

Zachowanie dokumentacji, postów na blogu oraz tekstu opisowego w zakresie od około 65 do 70ch sprawia, że strony te wyglądają zrównoważone i przyjazne od razu po otwarciu. Należy stosować to do bloków tekstu, a nie do całych układów.

Ustaw wszystkie odstępy według skali 4px/8px

Zeskanuj klasy pomocnicze w bazie kodu, która wydaje się nieuporządkowana, i często znajdziesz takie wartości:

  • odstępy w elementach typu „card” o wartości p-[18px]
  • odstęp od góry w modalu o wartości mt-5 (20px)
  • odstępy wokół przycisków o wartości px-3.5 py-[7px]
  • odstępy między sekcjami o wartości gap-7 (28px)

Każda z tych wartości została wybrana, ponieważ na jednym ekranie w danym momencie wyglądała dobrze, ale razem niszczą one rytm przestrzenny. Ludzie postrzegają spójne odstępy nawet bez ich zauważania, a gdy wartości tych odstępów nie są ze sobą powiązane, układ wydaje się zagracony i nieuporządkowany.

Skała wartości do przyjęcia

Ustalone systemy projektowe ograniczają odstępy do wielokrotności 4 lub 8 pikseli i nadają każdemu kroku nazwę oraz cel. Praktyczna skala wygląda w ten sposób:

  • space-1 (4px): mała odległość pomiędzy elementami, np. między ikoną a jej etykietą
  • space-2 (8px): kompaktowe odstępy dla naklejek i małych tagów
  • space-3 (12px): wewnętrzne odstępy w polach formularza
  • space-4 (16px): standardowe odstępy w kartach i przyciskach
  • space-6 (24px): odstępy między kartami
  • space-8 (32px): rozdzielenie poszczególnych sekcji
  • space-12 (48px): rozdzielenie głównych bloków na panelu kontrolnym

Wszystko, co wykracza poza tę skalę, np. margin-top: 19px, należy traktować jako błąd, a nie wybór stylu. Domyślna skala Tailwind już używa kroków o wielkości 4px, więc w praktyce zaleca się unikanie arbitralnych wartości.

Unikaj czarnych tłumieni w trybie ciemnym

Częstym pierwszym podejściem do stworzenia ciemnej interfejsu typu SaaS jest ustawienie strony na czysto czarny tło z tekstem w kolorze czystej bieli:

/* The Harsh Dark Mode Trap */
body {
  background-color: #000000;
  color: #ffffff;
}

Biały kolor na czarnym tle daje maksymalny możliwy stosunek kontrastu wynoszący 21:1. To z łatwością spełnia minimalne wymagania dostępności, ale przy tak ekstremalnym poziomie jasny tekst może wyglądać, jakby „przeciekał” lub świecił na tle (efekt halacji), co wielu czytelnikom, szczególnie tym z astygmatyzmem, sprawia trudności, zwłaszcza na ekranach OLED o wysokim kontrastie.

Bardziej poważnym problemem jest głębia. Powierzchnie znajdujące się bliżej odbiorcy lub źródła światła naturalnie wyglądają nieco jaśniej. Jeśli warstwa podstawowa ma już kolor #000000, nie pozostaje żadne miejsce na odróżnienie karty, menu rozwijanego czy okna modalnego, ponieważ czarny kolor nie może stać się jeszcze ciemniejszy pod nimi.

Stworzenie skalowania warstw powierzchni

Zamiast tego użyj głębokich, lekko zabarwionych tonów ciemnych i zwiększ jasność w miarę wzrostu elementów. Pierwsza część zestawu tokenów definiuje cztery poziomy powierzchni, od tła strony po nakładki takie jak okna modalne i podpowiedzi:

:root {
  /* Slate / Charcoal Depth Stack */
  --bg-canvas: #090d16;   /* Deepest background */
  --bg-surface: #0f172a;  /* Cards, tables, sidebar */
  --bg-elevated: #1e293b; /* Dropdowns, popovers, active tabs */
  --bg-overlay: #334155;  /* Modals, tooltips */

Pozostała część tego samego bloku :root dodaje dwa poziomy przenikliwości obramowania oraz trzy poziomy tekstu, od nagłówków po daty i nieaktywne ikony. Należy zauważyć, że w pokazanym fragmencie deklaracje --border-active i --text-primary znajdują się na jednej linii; jest to poprawny CSS, ale warto to przeformatować, a #f8fafc to po prostu kolor niemal biały, a nie biały o 95% przejrzystości:

  --border-subtle: rgba(255, 255, 255, 0.08);
  --border-active: rgba(255, 255, 255, 0.16);  --text-primary: #f8fafc;   /* 95% opacity white for headings */
  --text-secondary: #94a3b8; /* Muted slate for body text */
  --text-tertiary: #64748b;  /* Inactive icons, timestamps */
}

Gdy zostanie to zastosowane do struktury markup, tło znajduje się na dole, karta używa koloru powierzchni z delikatnym obramowaniem, a nagłówek i tekst używają tokenów tekstu pierwszorzędnego i drugorzędnego:

{/* Clean, layered elevation */}
<div className="bg-[var(--bg-canvas)] min-h-screen p-8">
  <div className="bg-[var(--bg-surface)] border border-[var(--border-subtle)] rounded-xl p-6 shadow-sm">
    <h2 className="text-[var(--text-primary)] font-semibold">
      Workspace Overview
    </h2>
    <p className="text-[var(--text-secondary)] text-sm mt-1">
      Manage team roles and API keys.
    </p>
  </div>
</div>

Hierarchia teraz opiera się wyłącznie na jasności, bez konieczności używania ciężkich cieni.

Zastąp jeden ciężki cień kilkoma jasnymi

Interfejsy amatorskie zazwyczaj używają jednego ciemnego, rozmytego cienia:

/* BAD: One thick, dark, muddy shadow */
.card-bad {
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.5);
}

Wynik przypomina przestarzały efekt edycji zdjęć. Prawdziwe obiekty nie rzucają jednolitego rozmycia. Fizyczny cień składa się z dwóch komponentów:

  • cienia o dużym kontraście, znajdującego się tuż obok obiektu i powstającego pod wpływem głównej źródła światła
  • szerokiego, miękkiego cienia, który stopniowo zanika w otoczeniu – nazywanego occlusionem otoczenia

Layuj cienie o niskich wartościach alfy

box-shadow przyjmuje listę oddzieloną przecinkami, więc można nałożyć dwa lub trzy cienie, z których każdy ma niską wartość alfy. Tutaj cień o szerokości 1px nadaje elementowi fundament, umiarkowane rozmycie dodaje głębię otoczenia, a szeroka, bardzo miękka warstwa rozprzestrzenia efekt:

/* Polished Enterprise Shadow */
.card-elevation-high {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.06),   /* Crisp grounding line */
    0 8px 16px rgba(0, 0, 0, 0.08),  /* Middle ambient blur */
    0 24px 48px rgba(0, 0, 0, 0.12); /* Soft dispersed glow */
}

Na ciemnych tłach cienie są trudne do zauważenia, dlatego wymagają wyższej przenikliwości, a cienka jasna obwódka znacznie ułatwia oddzielenie elementu. Wartości ujemne parametru spread zapobiegają rozprzestrzenianiu się ciemnego cienia poza jego granice:

/* In dark mode, pair subtle shadow with a crisp top border */
.card-dark-elevation {
  box-shadow:
    0 20px 25px -5px rgba(0, 0, 0, 0.5),
    0 8px 10px -6px rgba(0, 0, 0, 0.5);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

Zamiast wykroju z papieru umieszczonego na czarnym tle, element wygląda tak, jakby unosił się tuż nad płótnem.

Nigdy nie usuwaj obrysów skupienia bez zamiennika

Jedna linijka kodu w plikach stylów frontendowych powoduje więcej problemów niż jakakolwiek inna:

/* DO NOT DO THIS */
*:focus {
  outline: none;
}

Zespoły ją dodają, ponieważ domyślny pierścień skupienia przeglądarki koliduje z elementami o niestandardowym stylu. Jego usunięcie bez zapewnienia alternatywy uniemożliwia użytkownikom klawiatury zobaczenie, który element jest aktywny, co sprawia, że interfejs staje się dla nich praktycznie niewykorzystalny i narusza wymagania dostępności.

Zamiast tego użyj :focus-visible

Współczesne przeglądarki obsługują :focus-visible, który jest aktywny tylko wtedy, gdy przeglądarka uznaje za konieczne pokazanie wskaźnika skupienia, zwykle podczas nawigacji klawiaturą, a nie po kliknięciu myszką. Dzięki temu można pominąć ten wskaźnik dla użytkowników myszy, zachowując go jednak dla użytkowników klawiatury. Pierwszym krokiem jest usunięcie domyślnego obrysu z przycisków:

/* Remove ugly mouse clicks, preserve crystal-clear keyboard rings */
button:focus {
  outline: none;
}

Drugim krokiem jest zdefiniowanie wyraźnego, dostosowanego wskaźnika skupienia dla klawiatury:

button:focus-visible {
  outline: 2px solid #6366f1; /* Crisp indigo ring */
  outline-offset: 2px;
  border-radius: 6px;
}

Warto znać dwa ulepszenia. Po pierwsze, bezpieczniejszą wersją pierwszej zasady jest button:focus:not(:focus-visible), która usuwa obrys tylko wtedy, gdy nie jest potrzebny widoczny wskaźnik, dzięki czemu przeglądarki bez obsługi :focus-visible zachowują swój domyślny obrys. Po drugie, wartość border-radius w zasadzie skupienia zmienia kształt przycisku podczas jego aktywacji; jeśli przycisk ma już zaokrąglone rogi, należy pominąć tę linię, ponieważ nowoczesne przeglądarki rysują obrysy zgodnie z promieniem elementu.

W Tailwind tym samym pomysłem posługują się wariantami focus-visible:, które dodają obwód, odstęp oraz kolor odstępu dopasowany do ciemnego tła:

<button className="px-4 py-2 bg-indigo-600 hover:bg-indigo-500 rounded-lg text-white font-medium focus:outline-none focus-visible:ring-2 focus-visible:ring-indigo-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900 transition-all">
  Save Changes
</button>

Zachowanie właściwości outline-none w Tailwind zmieniło się pomiędzy głównymi wersjami (nowsze wydania dodają outline-hidden), więc sprawdź swoją wersję. W obu przypadkach użytkownicy myszy otrzymują czyste elementy sterowania, a użytkownicy klawiatury zawsze widzą, gdzie znajduje się fokus.

Przeprowadź te sprawdzenia przed wdrożeniem zmiany w interfejsie użytkownika:

  1. Szerokość tekstu: czy bloki tekstowe są ograniczone do około 50–75ch?
  2. Odstępy: czy wszystkie odstępy wewnętrzne, zewnętrzne i przerwy wynikają z ścisłej skali 4px/8px?
  3. Klatki w trybie ciemnym: czy powierzchnie są tworzone z warstw odcieni węgla lub slaty zamiast surowego koloru #000000?
  4. Cienie: czy cienie są miękkie, warstwowe i rozproszone, a nie pojedynczym ciemnym zamazaniem?
  • Stany fokusu: czy ktoś może obsługiwać cały ekran za pomocą klawisza Tab i zawsze widzieć, gdzie znajduje się fokus?
  • Podsumowanie

    Język polski powstaje mniej dzięki intuicji artystycznej, a bardziej dzięki spójnym ograniczeniom: długości wierszy oparte na ch, stała skala odstępów, jasność jako element głębi w ciemnych tematach, cienie naśladujące prawdziwe światło oraz pierścienie fokusu, które pozostają widoczne. Każde z tych rozwiązań to prosta, poddawalna weryfikacji zasada, dzięki której można je egzekwować za pomocą tokenów, reguł lintingu i listy kontrolnej, zamiast polegać na subiektywnym gustie przy każdym pull requestu.

    Literatura pokrewna

  • Dziewięć technik modu ciemnego porównanych, od hacków filtrów do plików cookies serwera — Porównaj dziewięć sposobów na dodanie modu ciemnego do aplikacji internetowej, od odwracania filtrów po tokeny, funkcję light-dark() oraz pliki cookies serwera, i dowiedz się, jakie błędy potajemnie wprowadza każda z nich.