Kurs HTML i CSS · Moduł 4: Flexbox

Order i Align-self - indywidualna kontrola elementów

9 min czytania ·
W tej lekcji5

Witaj ponownie w Bibliotece Aleksandryjskiej! Na telefonie treść powinna stanąć przed menu bocznym, choć HTML ma zostać nietknięty. Bibliotekarze znali ten problem: na wizytę faraona najcenniejszy papirus trafiał na przód półki, a katalog zostawał bez zmian. We Flexboksie takie wyjątki załatwiają order i align-self.

Order - zmiana kolejności wizualnej

Właściwość order zmienia kolejność wyświetlania bez zmiany HTML. Przyjmuje liczbę całkowitą, a na starcie wszystkie elementy mają tę samą wartość:

.item {
  order: 0;  /* Domyślna wartość dla wszystkich elementów */
}

/* Niższa wartość = wcześniej */
/* Wyższa wartość = później */

Elementy są sortowane rosnąco, przy remisie decyduje kolejność w HTML, a wartości ujemne, jak order: -1, są dozwolone.

Przykład podstawowy

Oto cztery pudełka, które HTML wypisuje po kolei:

<div class="container">
  <div class="item-1">1</div>
  <div class="item-2">2</div>
  <div class="item-3">3</div>
  <div class="item-4">4</div>
</div>

Teraz w CSS trójka dostaje wartość ujemną, jedynka dodatnią, a reszta zostaje przy domyślnym zerze:

.container {
  display: flex;
}

/* Domyślnie: 1, 2, 3, 4 */

.item-3 {
  order: -1;  /* Będzie pierwszy: 3, 1, 2, 4 */
}

.item-1 {
  order: 1;  /* Będzie ostatni: 3, 2, 4, 1 */
}

/* Wynik: 3, 2, 4, 1 */

Trójka wskakuje na początek, jedynka na koniec, a dwójka i czwórka zachowują kolejność z HTML, który wcale się nie zmienił.

Praktyczny przykład - responsywny layout

Siła order ujawnia się w układach zmieniających się razem z ekranem:

<div class="layout">
  <header class="header">Header</header>
  <nav class="sidebar">Sidebar</nav>
  <main class="content">Content</main>
  <aside class="ads">Ads</aside>
  <footer class="footer">Footer</footer>
</div>

W CSS pojawią się narzędzia z kolejnych lekcji: flex-wrap: wrap przenosi sekcje do nowego rzędu, flex: 0 0 100% zajmuje całą szerokość, flex: 1 bierze resztę miejsca, a blok @media (max-width: 768px) działa tylko na ekranach do 768 pikseli:

.layout {
  display: flex;
  flex-wrap: wrap;
}

/* Desktop - domyślna kolejność */
.header { order: 1; flex: 0 0 100%; }
.sidebar { order: 2; flex: 0 0 25%; }
.content { order: 3; flex: 1; }
.ads { order: 4; flex: 0 0 20%; }
.footer { order: 5; flex: 0 0 100%; }

/* Mobile - zmień kolejność */
@media (max-width: 768px) {
  .header { order: 1; }
  .content { order: 2; }  /* Content przed sidebar! */
  .sidebar { order: 3; }
  .ads { order: 4; }
  .footer { order: 5; }

  /* Wszystkie pełna szerokość */
  .header, .sidebar, .content, .ads, .footer {
    flex: 0 0 100%;
  }
}

Na telefonie treść z order: 2 wskakuje przed menu boczne, a HTML stoi w miejscu, jak piramida, w której faraon zmienił tylko trasę zwiedzania. Moja rada: nadawaj order tylko sekcjom, które mają się przesunąć, a resztę zostaw przy zerze.

Praktyczny przykład - sortowanie produktów

W sklepie wyróżnione produkty mają stanąć na przedzie:

<div class="products">
  <div class="product" data-priority="normal">Produkt A</div>
  <div class="product featured" data-priority="high">Produkt B</div>
  <div class="product" data-priority="normal">Produkt C</div>
  <div class="product sale" data-priority="medium">Produkt D</div>
</div>

CSS reaguje tu na klasy featured i sale, a data-priority jest tylko opisem. gap: 20px robi odstępy, a calc(33.333% - 14px) to jedna trzecia szerokości minus udział w nich:

.products {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

.product {
  flex: 0 0 calc(33.333% - 14px);
  order: 3;  /* Normalne produkty na końcu */
}

.product.featured {
  order: 1;  /* Wyróżnione na początku */
  border: 3px solid goldenrod;
}

.product.sale {
  order: 2;  /* Wyprzedaż w środku */
  background: #e74c3c;
  color: white;
}

/* Wynik: Featured → Sale → Normal → Normal */

Wyróżniony produkt B staje pierwszy, przeceniony D drugi, a A i C zamykają szereg w kolejności z HTML.

Uwagi o dostępności

Jest haczyk: order zmienia tylko wizualną kolejność, a nie DOM, po którym poruszają się klawiatura i czytniki ekranu. Weźmy trzy zakładki:

<div class="tabs">
  <button>Tab 1</button>
  <button>Tab 2</button>
  <button>Tab 3</button>
</div>

W HTML przycisk Tab 1 stoi na początku. Przestawmy go wizualnie na sam koniec paska:

.tabs button:first-child {
  order: 3;  /* Wizualnie ostatni */
}

/* Problem: Nawigacja klawiaturą (Tab) zachowa oryginalną kolejność! */

Na ekranie Tab 1 jest ostatni, ale klawiatura i czytnik ekranu nadal trafią na niego najpierw. Specyfikacja Flexboxa mówi wprost: order służy do zmian wizualnych, nie logicznych, więc gdy kolejność zmienia sens treści, przestaw HTML i sprawdź układ samą klawiaturą.

Align-self - indywidualne wyrównanie

align-self działa w poprzek osi głównej: nadpisuje align-items kontenera dla pojedynczego elementu.

Wartości align-self

Przyjmuje wartości znane z align-items oraz auto:

.item {
  align-self: auto;          /* Dziedziczy z align-items (default) */
  align-self: flex-start;    /* Na początku osi poprzecznej */
  align-self: flex-end;      /* Na końcu osi poprzecznej */
  align-self: center;        /* Wyśrodkowany */
  align-self: baseline;      /* Wyrównany do linii bazowej tekstu */
  align-self: stretch;       /* Rozciągnięty (jeśli brak height) */
}

Domyślne auto przejmuje align-items rodzica, a stretch rozciąga element tylko wtedy, gdy nie ma ustalonej wysokości.

Przykład podstawowy

Środkowy z trzech elementów dostał dodatkową klasę special:

<div class="container">
  <div class="item">Item 1</div>
  <div class="item special">Item 2 (Special)</div>
  <div class="item">Item 3</div>
</div>

Kontener wyśrodkowuje wszystkich przez align-items: center, a potem robimy jeden wyjątek dla wyróżnionego elementu:

.container {
  display: flex;
  align-items: center;  /* Wszystkie wyśrodkowane */
  height: 300px;
  border: 2px solid #ddd;
}

.item {
  padding: 20px;
  background: #3498db;
  color: white;
}

/* Jeden element inaczej wyrównany */
.item.special {
  align-self: flex-start;  /* Ten będzie na górze */
  background: goldenrod;
}

Item 2 przykleja się do górnej krawędzi, a pozostałe zostają na środku, bo reguła kontenera nadal obowiązuje.

Praktyczne przykłady

1. Card z przyciskiem na dole

Przyciski w kartach o różnej treści mają stać równo na dole:

<div class="cards">
  <div class="card">
    <h3>Produkt 1</h3>
    <p>Krótki opis</p>
    <button class="btn">Kup teraz</button>
  </div>
  <div class="card">
    <h3>Produkt 2</h3>
    <p>Bardzo długi opis, który zajmuje dużo więcej miejsca...</p>
    <button class="btn">Kup teraz</button>
  </div>
</div>

Każda karta zostaje kontenerem flex w kolumnie, a przycisk dostaje align-self: stretch i margin-top: auto, czyli margines zjadający wolne miejsce nad nim:

.cards {
  display: flex;
  gap: 20px;
}

.card {
  flex: 1;
  display: flex;
  flex-direction: column;
  padding: 20px;
  border: 1px solid #ddd;
}

.card h3,
.card p {
  flex: 0;
}

.card .btn {
  align-self: stretch;  /* Przycisk na pełną szerokość */
  margin-top: auto;  /* Przesuń na dół */
}

W kolumnie oś poprzeczna biegnie poziomo, więc przycisk rozciąga się na szerokość karty, a margines spycha go na dół.

2. Avatar w chat message

W dymku czatu zdjęcie autora stoi obok treści i godziny:

<div class="message">
  <img src="avatar.jpg" alt="User" class="avatar">
  <div class="message-content">
    <h4>User Name</h4>
    <p>To jest wiadomość, która może być długa...</p>
    <span class="timestamp">10:30</span>
  </div>
</div>

Kontener ustawia wszystko na górze, awatar dostaje wyjątek align-self: center, a flex-shrink: 0 chroni go przed zgnieceniem:

.message {
  display: flex;
  gap: 15px;
  align-items: flex-start;  /* Wszystko na górze */
  padding: 15px;
}

.avatar {
  width: 50px;
  height: 50px;
  border-radius: 50%;
  flex-shrink: 0;
  align-self: center;  /* Avatar wyśrodkowany pionowo */
}

.message-content {
  flex: 1;
}

.timestamp {
  align-self: flex-end;  /* Timestamp na dole */
  font-size: 0.8em;
  color: #777;
}

Awatar stoi pośrodku, ale uwaga na pułapkę: align-self działa tylko na dzieci kontenera flex, a .timestamp leży w .message-content bez display: flex, więc godzina się nie ruszy. Dopisz tam display: flex i flex-direction: column, a flex-end przesunie godzinę do prawej krawędzi.

3. Icon toolbar

Na pasku narzędzi jedna ikona jest większa od pozostałych:

<div class="toolbar">
  <button class="tool">Icon 1</button>
  <button class="tool">Icon 2</button>
  <button class="tool large">Big Icon</button>
  <button class="tool">Icon 3</button>
</div>

Pasek wyśrodkowuje ikony, a duża dostaje wyjątek align-self: flex-start oraz złote tło:

.toolbar {
  display: flex;
  gap: 10px;
  padding: 10px;
  background: #34495e;
  align-items: center;  /* Wszystkie wyśrodkowane */
}

.tool {
  width: 40px;
  height: 40px;
  background: #3498db;
  border: none;
  color: white;
  cursor: pointer;
}

.tool.large {
  width: 60px;
  height: 60px;
  align-self: flex-start;  /* Wyrównany do góry */
  background: goldenrod;
}

Duża ikona jest najwyższa, więc flex-start wygląda tu jak center, dopóki pasek nie dostanie height: 80px.

Order + Align-self - kombinacja

W sekcji hero obie właściwości działają razem:

<div class="hero">
  <div class="hero-image">Image</div>
  <div class="hero-content">Content</div>
</div>

Obraz z order: 2 trafia na prawo, treść z order: 1 i align-self: flex-start na lewo, a na telefonie kolejność się odwraca:

.hero {
  display: flex;
  align-items: center;
  min-height: 500px;
  gap: 40px;
}

.hero-image {
  flex: 0 0 50%;
  order: 2;  /* Image po prawej */
}

.hero-content {
  flex: 1;
  order: 1;  /* Content po lewej */
  align-self: flex-start;  /* Content na górze */
}

/* Na mobile */
@media (max-width: 768px) {
  .hero {
    flex-direction: column;
  }

  .hero-image {
    order: 1;  /* Image na górze */
    flex: 0 0 auto;
  }

  .hero-content {
    order: 2;  /* Content na dole */
    align-self: stretch;  /* Pełna szerokość */
  }
}

Na telefonie obraz wskakuje na górę, a treść pod nim rozciąga się na pełną szerokość, bo w kolumnie stretch działa poziomo.

Praktyczne wzorce

Wyróżniony element zawsze otwiera galerię i zajmuje cały rząd:

.grid {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
}

.grid-item {
  flex: 0 0 calc(33.333% - 14px);
  order: 2;
}

.grid-item.featured {
  flex: 0 0 100%;
  order: 1;  /* Zawsze pierwszy */
  align-self: stretch;
  background: goldenrod;
}

Ma order: 1, a reszta order: 2, więc jego miejsce w HTML nie ma znaczenia.

Stopka karty ma trzymać się dołu:

.card {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.card-header {
  flex: 0 0 auto;
}

.card-body {
  flex: 1;
}

.card-footer {
  flex: 0 0 auto;
  align-self: stretch;
  margin-top: auto;  /* Zawsze na dole */
}

Środek z flex: 1 zabiera wolne miejsce, a margin-top: auto przykleja stopkę do dołu.

3. Responsive form

Przycisk wysyłania zawsze zamyka formularz:

.form {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

.form-field {
  flex: 0 0 calc(50% - 7.5px);
}

.form-field.full {
  flex: 0 0 100%;
}

.form-submit {
  flex: 0 0 auto;
  order: 999;  /* Zawsze na końcu */
  align-self: flex-end;
  margin-top: 10px;
}

@media (max-width: 768px) {
  .form-field {
    flex: 0 0 100%;
  }

  .form-submit {
    flex: 0 0 100%;  /* Pełna szerokość na mobile */
    align-self: stretch;
  }
}

Przycisk z order: 999 ląduje na końcu. Na telefonie dostaje flex: 0 0 100%, bo samo align-self: stretch rozciąga go w wierszu tylko w pionie.

Podsumowanie

order zmienia tylko wizualną kolejność i nie rusza DOM, a align-self nadpisuje align-items jednego elementu w osi poprzecznej. W następnej lekcji zobaczysz, co się dzieje, gdy elementy nie mieszczą się w rzędzie.

W edytorze poniżej zmieniaj wartości order i ułóż dynastię faraonów, nie ruszając HTML.

Kod do tej lekcji: index.html
<!DOCTYPE html>
<html lang="pl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flexbox - Order</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="container">
        <h1>Order - Kolejność elementów</h1>
        <p class="subtitle">Zmień kolejność wyświetlania bez zmiany HTML</p>

        <section class="demo-section">
            <h2>Domyślna kolejność (order: 0)</h2>
            <div class="flex-container">
                <div class="flex-item order-0">Element 1</div>
                <div class="flex-item order-0">Element 2</div>
                <div class="flex-item order-0">Element 3</div>
                <div class="flex-item order-0">Element 4</div>
            </div>
            <p class="description">Elementy w kolejności z HTML</p>
        </section>

        <section class="demo-section">
            <h2>Zmieniona kolejność</h2>
            <div class="flex-container">
                <div class="flex-item order-4">Element 1 (order: 4)</div>
                <div class="flex-item order-2">Element 2 (order: 2)</div>
                <div class="flex-item order-1">Element 3 (order: 1)</div>
                <div class="flex-item order-3">Element 4 (order: 3)</div>
            </div>
            <p class="description">Wyświetlanie: 3 → 2 → 4 → 1</p>
        </section>

        <section class="demo-section">
            <h2>Order z wartościami ujemnymi</h2>
            <div class="flex-container">
                <div class="flex-item order-0">Normal (0)</div>
                <div class="flex-item order-minus">Pierwszy (-1)</div>
                <div class="flex-item order-0">Normal (0)</div>
                <div class="flex-item order-plus">Ostatni (2)</div>
            </div>
            <p class="description">Order może być ujemny - pojawi się przed elementami z order: 0</p>
        </section>

        <section class="demo-section practice">
            <h2>Praktyka: Dynastia Faraonów</h2>
            <p class="practice-description">Uporządkuj faraonów według chronologii panowania</p>

            <div class="timeline-container">
                <h3>Chronologiczna lista faraonów:</h3>
                <div class="timeline">
                    <div class="pharaoh narmer">
                        <div class="crown">▲</div>
                        <div class="name">Narmer</div>
                        <div class="years">~3100 p.n.e.</div>
                        <div class="dynasty">I Dynastia</div>
                    </div>

                    <div class="pharaoh khufu">
                        <div class="crown">▲</div>
                        <div class="name">Cheops (Khufu)</div>
                        <div class="years">~2580 p.n.e.</div>
                        <div class="dynasty">IV Dynastia</div>
                    </div>

                    <div class="pharaoh tutankhamun">
                        <div class="crown">▲</div>
                        <div class="name">Tutanchamon</div>
                        <div class="years">~1332 p.n.e.</div>
                        <div class="dynasty">XVIII Dynastia</div>
                    </div>

                    <div class="pharaoh ramesses">
                        <div class="crown">▲</div>
                        <div class="name">Ramzes II</div>
                        <div class="years">~1279 p.n.e.</div>
                        <div class="dynasty">XIX Dynastia</div>
                    </div>

                    <div class="pharaoh cleopatra">
                        <div class="crown">▲</div>
                        <div class="name">Kleopatra VII</div>
                        <div class="years">~51 p.n.e.</div>
                        <div class="dynasty">Ptolemeusze</div>
                    </div>
                </div>
            </div>

            <div class="controls">
                <button onclick="resetOrder()">Kolejność HTML</button>
                <button onclick="chronologicalOrder()">Chronologicznie</button>
                <button onclick="reverseOrder()">Odwrotnie</button>
                <button onclick="randomOrder()">Losowo</button>
            </div>

            <div class="info-box">
                <h4>Jak działa order:</h4>
                <ul>
                    <li>Domyślnie wszystkie elementy mają <code>order: 0</code></li>
                    <li>Elementy sortowane są rosnąco według wartości order</li>
                    <li>Przy tej samej wartości order, liczy się kolejność w HTML</li>
                    <li>Można używać wartości ujemnych</li>
                </ul>
            </div>
        </section>
    </div>

    <script>
        function resetOrder() {
            const pharaohs = document.querySelectorAll('.pharaoh');
            pharaohs.forEach((pharaoh, index) => {
                pharaoh.style.order = '0';
            });
        }

        function chronologicalOrder() {
            document.querySelector('.narmer').style.order = '1';
            document.querySelector('.khufu').style.order = '2';
            document.querySelector('.tutankhamun').style.order = '3';
            document.querySelector('.ramesses').style.order = '4';
            document.querySelector('.cleopatra').style.order = '5';
        }

        function reverseOrder() {
            document.querySelector('.narmer').style.order = '5';
            document.querySelector('.khufu').style.order = '4';
            document.querySelector('.tutankhamun').style.order = '3';
            document.querySelector('.ramesses').style.order = '2';
            document.querySelector('.cleopatra').style.order = '1';
        }

        function randomOrder() {
            const pharaohs = document.querySelectorAll('.pharaoh');
            pharaohs.forEach(pharaoh => {
                pharaoh.style.order = Math.floor(Math.random() * 10);
            });
        }
    </script>
</body>
</html>

Pamiętaj: order i align-self to specjalne rozkazy faraona dla pojedynczych bloków piramidy, wyjątki od ogólnych zasad, które nigdy nie przestawiają katalogu, czyli HTML.

Hasła z dokumentacji w tej lekcji

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Jaką domyślną wartość właściwości order mają wszystkie elementy flex?

  2. 2. Do czego służy właściwość align-self?

Zadania praktyczne w grze

  • Układanie w poziomie

    Ułóż deklarację CSS zmieniającą kolejność elementu na drugą pozycję:

  • Klikanie w kolejności

    Ułóż deklarację, która wyśrodkowuje pojedynczy element na osi poprzecznej:

Przydatne artykuły