По-настоящему адаптивный интерфейс

Рома Ахмадуллин

Дром

/ Я 💛 Фронтенд 2026

По-настоящему адаптивный интерфейс

Рома Ахмадуллин

Лид фронтенда, Дром

Рома Ахмадуллин

Дром — автомобильный классифайд

Рома Ахмадуллин

Дока — добрая энциклопедия про веб

Рома Ахмадуллин

Рома Ахмадуллин

Responsive Web Design

Брейкпоинты

 Брейкпоинты 

Компоненты

Компоненту не важен вьюпорт

Важен контекст

🤔 Что нужно учесть?

Контекст включает в себя

            
                .card {
                    display: flex;

                    .image {
                        width: 150px;
                        height: 100%;
                    }
                }

                @media (width <= 375px) {
                    .card {
                        flex-direction: column;

                        .image {
                            width: 100%;
                            height: 100px;
                        }
                    }
                }
            
        
            
                .card {
                    display: flex;

                    .image {
                        width: 150px;
                        height: 100%;
                    }
                }

                @media (width <= 375px) {
                    .card {
                        flex-direction: column;

                        .image {
                            width: 100%;
                            height: 100px;
                        }
                    }
                }
            
        
            
                .card {
                    display: flex;

                    .image {
                        width: 150px;
                        height: 100%;
                    }
                }

                @media (width <= 375px) {
                    .card {
                        flex-direction: column;

                        .image {
                            width: 100%;
                            height: 100px;
                        }
                    }
                }
            
        

Контекст включает в себя

Контекст включает в себя

Контекст включает в себя

Контекст включает в себя

Адаптив

Вьюпорт

Контекст

Адаптив

Вьюпорт

Контекст

Адаптив

Вьюпорт

Контекст

🪛 Как реализовать?

Адаптив Размер контейнера

Container queries

Container Queries

Механизм, позволяющий применять стили к элементу в зависимости от размеров его контейнера.

Спецификация: CSS Conditional Rules Module Level 5
CSS-спецификация: Container Queries
Can I use: Container Queries
            
            <div class="container">
                <div class="element"></div>
            </div>
            
        
            
                .container {
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-name: card;
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container card (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-name: card;
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container card (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-name: card;
                    container-type: inline-size;
                }

                .element {
                    color: blue;
                }

                @container card (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-name: card;
                    container-type: inline-size;
                    container: card / inline-size;
                }

                .element {
                    color: blue;
                }

                @container card (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        
            
                .container {
                    container-name: card;
                    container-type: inline-size;
                    container: card / inline-size;
                }

                .element {
                    color: blue;
                }

                @container card (width <= 200px) {
                    .element {
                        color: green;
                    }
                }
            
        

container-type

container-type

container-type

container-type

        
        <article class="card">
            <div class="card-content">
                <img src="pic.jpg" class="image">
                <div class="text-container">
                    <h3 class="title"></h3>
                    <p class="text"></p>
                    <a href="site.com" class="link"></a>
                </div>
            </div>
        </article>
        
        
        
        .card {
            container: card / inline-size;
        }

        .card-content {
            display: flex;
            gap: 20px;
            padding: 10px;
            font-size: 16px;
        }

        @container card (width <= 200px) {
            .card-content {
                flex-direction: column;
                gap: 10px;
                font-size: 18px;
            }
        }
        
        

В чём профит?

Дизайнер

Давай обновим сетку!

✌🏻 Две заметки про размер

Контент не влияет на размеры контейнера

        
        .container {
            
            display: block;
            border: 2px solid white;
            padding: 20px;
        }
        
        

container-type  ➡️  contain

Rachel Andrew: Статья про contain

Размеры контейнера
===
Размерам content-box

        
        .container {
            container-type: inline-size;
            border: 2px solid white;
            padding: 20px;
        }

        .element {
            background: green;
        }

        @container (width <= 300px) {
            .element {
                background: yellow;
            }
        }
        
        

📐 Container query units

Container query units

Container query units

Container query units

Container query units

Интерактивный гайд по Container Queires

Типографика

Responsive Typography

        
        .text {
            font-size: 30px;
        }

        @container card (width <= 600px) {
            .text {
                font-size: 20px;
            }
        }

        @container card (width <= 400px) {
            .text {
                font-size: 18px;
            }
        }
        
        

Fluid Typography

Как реализовать?

clamp()

Позволяет ограничивать диапазон изменения некоего значения, задавая его нижний и верхний пределы.

Спецификация: CSS Values and Units Module Level 4
CSS-спецификация: clamp()
Can I use: clamp()
    
    clamp(min, preferred, max)
    
    
    
    clamp(min, preferred, max)
    
    
    
    clamp(min, preferred, max)
    
    
    
    clamp(min, preferred, max)
    
    
    
    clamp(18px, 24px, 30px)
    
    
    
    clamp(1rem, 1.5rem, 2rem)
    
    
    
    clamp(1rem, 0.5rem + 3cqw, 2rem)
    
    
    
    clamp(1rem, 0.5rem + 3cqw, 2rem)
    
    
    
    clamp(1rem, 0.5rem + 3cqw, 2rem)
    
    
    
    
    clamp(1rem, 0.5rem + 3cqw, 2rem)
    
    
    
    
Татьяна Фокина: Fluid Typography and CSS clamp()

Адаптив Контент

Style queries

Style Queries

Механизм, позволяющий применять стили к элементу в зависимости от значений вычисленных CSS-свойств его контейнера.

Спецификация: CSS Conditional Rules Module Level 5
CSS-спецификация: Style Queries
Can I use: Style Queries
        
            .container {
                container-type: normal;
                container-name: card;
            }

            .element {
                color: blue;
            }

            @container card style(--feature: true) {
                .element {
                    color: green;
                }
            }
        
        
        
            .container {
                container-type: normal;
                container-name: card;
            }

            .element {
                color: blue;
            }

            @container card style(--feature: true) {
                .element {
                    color: green;
                }
            }
        
        
        
            .container {
                container-type: normal;
                container-name: card;
            }

            .element {
                color: blue;
            }

            @container card style(--feature: true) {
                .element {
                    color: green;
                }
            }
        
        
        
            .container {
                container-type: normal;
                container-name: card;
            }

            .element {
                color: blue;
            }

            @container card style(--feature: true) {
                .element {
                    color: green;
                }
            }
        
        
        
            .container {
                container-type: normal;
                container-name: card;
            }

            .element {
                color: blue;
            }

            @container card style(--feature: true) {
                .element {
                    color: green;
                }
            }
        
        
        
        <div class="card">
            ...
        </div>
        <div class="card">
            ...
        </div>
        <div class="card">
            ...
        </div>
        
        
        
        <div class="card" style="--label: 'новый';">
            ...
        </div>
        <div class="card">
            ...
        </div>
        <div class="card">
            ...
        </div>
        
        
        
            .card {
                container-name: card;
            }
    
            @container card style(--label: 'новый') {
                .card-content::after {
                    content: 'новый';
                    background-color: green;
                }
            }
        
        
        
        <div class="weather-card" style="--snow: true;">
            ...
        </div>
        <div class="weather-card" style="--cloudy: true;">
            ...
        </div>
        <div class="weather-card" style="--cloudy: true; --sunny: true;">
            ...
        </div>
        <div class="weather-card" style="--sunny: true;">
            ...
        </div>
        
        
        
        @container style(--snow: true) {
            /* меняем цвет фона и иконку */
        }

        @container style(--cloudy: true) {
            ...
        }

        @container style(--sunny: true) {
            ...
        }

        @container style(--sunny: true) and style(--cloudy: true) {
            ...
        }
        
        
        
        @container style(--snow: true) {
            /* меняем цвет фона и иконку */
        }

        @container style(--cloudy: true) {
            ...
        }

        @container style(--sunny: true) {
            ...
        }

        @container style(--sunny: true) and style(--cloudy: true) {
            ...
        }
        
        

Container queries + Style queries

        
        .card {
            container: card / inline-size;
        }

        .card-layout {
            /* дефолтные стили */
        }

        @container card (width <= 414px) and style(--vertical: true) {
            .card-layout {
                /* стили для вертикальной карточки */
            }
        }
        
        
        
        .card {
            container: card / inline-size;
        }

        .card-layout {
            /* дефолтные стили */
        }

        @container card (width <= 414px) and style(--vertical: true) {
            .card-layout {
                /* стили для вертикальной карточки */
            }
        }
        
        

🚶🏼‍♂️ Идём дальше

        

            .parent > hr {
                height: 5px;
            }


            .parent hr ~ p {
                color: green;
            }
            
            
            .parent hr + p {
                color: yellow;
            }
        
        
        
            /* hr непосредственно внутри parent */
            .parent > hr {
                height: 5px;
            }


            .parent hr ~ p {
                color: green;
            }
            
            
            .parent hr + p {
                color: yellow;
            }
        
        
        
            /* hr непосредственно внутри parent */
            .parent > hr {
                height: 5px;
            }

            /* все параграфы после hr */
            .parent hr ~ p {
                color: green;
            }
            

            .parent hr + p {
                color: yellow;
            }
        
        
        
            /* hr непосредственно внутри parent */
            .parent > hr {
                height: 5px;
            }

            /* все параграфы после hr */
            .parent hr ~ p {
                color: green;
            }
            
            /* первый параграф после hr */
            .parent hr + p {
                color: yellow;
            }
        
        
        
            /* hr непосредственно внутри parent */
            .parent > hr {
                height: 5px;
            }

            /* все параграфы после hr */
            .parent hr ~ p {
                color: green;
            }
            
            /* первый параграф после hr */
            .parent hr + p {
                color: yellow;
            }
        
        
        
            /* hr непосредственно внутри parent */
            .parent > hr {
                height: 5px;
            }

            /* все параграфы после hr */
            .parent hr ~ p {
                color: green;
            }
            
            /* первый параграф после hr */
            .parent hr + p {
                color: yellow;
            }
        
        

Как стилизовать родителя?

:has()

:has()

Позволяет стилизовать родительский элемент при наличии конкретного дочернего элемента.

Спецификация: Selectors Level 4
CSS-спецификация: :has()
Can I use: :has()
        
        .parent:has(.child) {
            /* стили для родителя */
        }
        
        
        
        .parent:has(.child) {
            /* стили для родителя */
        }
        
        
        
        .parent:has(.child) {
            /* стили для родителя */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card with-image">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card.with-image {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card with-image">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card.with-image {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card with-image">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card.with-image {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card with-image">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card.with-image {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card with-image">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card.with-image {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card:has(img) {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card:has(img) {
            /* стили для кейса с карточкой */
        }
        
        
        
        <div class="card">
            ...
        </div>

        <div class="card">
            <img src="pic.jpg">
            ...
        </div>

        .card {
            /* дефолтные стили */
        }

        .card:has(img) {
            /* стили для кейса с карточкой */
        }
        
        
        
        .file {
            background-image: var(--bg);
        }

        .file:has([href$=".pdf"]) {
            --bg: 'icon-pdf.svg';
        }

        .file:has([href$=".ppt"]) {
            --bg: 'icon-ppt.svg';
        }

        .file:has([href$=".docx"]) {
            --bg: 'icon-docx.svg';
        }
        
        
        
        .file {
            background-image: var(--bg);
        }

        .file:has([href$=".pdf"]) {
            --bg: 'icon-pdf.svg';
        }

        .file:has([href$=".ppt"]) {
            --bg: 'icon-ppt.svg';
        }

        .file:has([href$=".docx"]) {
            --bg: 'icon-docx.svg';
        }
        
        
        
        .file {
            background-image: var(--bg);
        }

        .file:has([href$=".pdf"]) {
            --bg: 'icon-pdf.svg';
        }

        .file:has([href$=".ppt"]) {
            --bg: 'icon-ppt.svg';
        }

        .file:has([href$=".docx"]) {
            --bg: 'icon-docx.svg';
        }
        
        
Интерактивный гайд по :has()

Адаптив Окружающая среда

        
        .card {
            position: relative;
            padding: 20px;
            padding-right: 70px;
        }

        .icon {
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        html[dir="ltr"] .card {
            position: relative;
            padding: 20px;
            padding-right: 70px;
        }

        html[dir="ltr"] .icon {
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        html[dir="rtl"] .card {
            position: relative;
            padding: 20px;
            padding-left: 70px;
        }

        html[dir="rtl"] .icon {
            position: absolute;
            top: 20px;
            left: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        html[dir="ltr"] .card {
            position: relative;
            padding: 20px;
            padding-right: 70px;
        }

        html[dir="ltr"] .icon {
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        html[dir="rtl"] .card {
            position: relative;
            padding: 20px;
            padding-left: 70px;
        }

        html[dir="rtl"] .icon {
            position: absolute;
            top: 20px;
            left: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        html[dir="ltr"] .card {
            position: relative;
            padding: 20px;
            padding-right: 70px;
        }

        html[dir="ltr"] .icon {
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        html[dir="rtl"] .card {
            position: relative;
            padding: 20px;
            padding-left: 70px;
        }

        html[dir="rtl"] .icon {
            position: absolute;
            top: 20px;
            left: 20px;
            width: 40px;
            height: 40px;
        }
        
        

Добавлять третью вилку?

Логические свойства

Логические свойства

Позволяют описывать размеры и отступы на основе логических значений — основанных на направлении текста и режима письма.

Спецификация: CSS Logical Properties and Values Module Level 1
CSS-спецификация: Логические свойства
Can I use: Logical properties

Группы логических свойств

Группы логических свойств

Группы логических свойств

Группы логических свойств

Свойства с логическими эквивалентами

        
        .card {
            position: relative;
            padding: 20px;
            padding-right: 70px;
        }

        .icon {
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        .card {
            position: relative;
            padding: 20px;
            padding-right: 70px;
        }

        .icon {
            position: absolute;
            top: 20px;
            right: 20px;
            width: 40px;
            height: 40px;
        }
        
        
        
        .card {
            position: relative;
            padding: 20px;
            padding-inline-end: 70px;
        }

        .icon {
            position: absolute;
            inset-block-start: 20px;
            inset-inline-end: 20px;
            inline-size: 40px;
            block-size: 40px;
        }
        
        
        
        .card {
            position: relative;
            padding: 20px;
            padding-inline-end: 70px;
        }

        .icon {
            position: absolute;
            inset-block-start: 20px;
            inset-inline-end: 20px;
            inline-size: 40px;
            block-size: 40px;
        }
        
        
Дока: Логические свойства CSS

Anchor Positioning

Anchor Positioning

Определяет, как элемент может менять свой размер
и позиционироваться на странице относительно одного или нескольких якорей.

Спецификация: CSS Anchor Positioning Module Level 1
CSS-спецификация: anchor positioning
Can I use: Anchor Positioning
        
            .anchor {
                anchor-name: --my-anchor;
            }

            .tooltip {
                position: fixed;
                position-anchor: --my-anchor;
                position-area: top;
                position-try-fallbacks: flip-block;
            }
        
        
        
            .anchor {
                anchor-name: --my-anchor;
            }

            .tooltip {
                position: fixed;
                position-anchor: --my-anchor;
                position-area: top;
                position-try-fallbacks: flip-block;
            }
        
        
        
            .anchor {
                anchor-name: --my-anchor;
            }

            .tooltip {
                position: fixed;
                position-anchor: --my-anchor;
                position-area: top;
                position-try-fallbacks: flip-block;
            }
        
        
        
            .anchor {
                anchor-name: --my-anchor;
            }

            .tooltip {
                position: fixed;
                position-anchor: --my-anchor;
                position-area: top;
                position-try-fallbacks: flip-block;
            }
        
        
        
            .anchor {
                anchor-name: --my-anchor;
            }

            .tooltip {
                position: fixed;
                position-anchor: --my-anchor;
                position-area: top;
                position-try-fallbacks: flip-block;
            }
        
        
        
        .tooltip::before {
            content: '▼';
            position: absolute;
            top: 100%;
        }
        
        
            
                .tooltip {
                    ...
                    position-try-fallbacks: flip-block;
                    container-type: anchored;
                }

                .tooltip::before {
                    content: '▼';
                    position: absolute;
                    top: 100%;
                }
        
                @container anchored(fallback: flip-block) {
                    .tooltip::before {
                        content: '▲';
                        top: auto;
                        bottom: 100%;
                    }
                }
            
        
            
                .tooltip {
                    ...
                    position-try-fallbacks: flip-block;
                    container-type: anchored;
                }

                .tooltip::before {
                    content: '▼';
                    position: absolute;
                    top: 100%;
                }
        
                @container anchored(fallback: flip-block) {
                    .tooltip::before {
                        content: '▲';
                        top: auto;
                        bottom: 100%;
                    }
                }
            
        
            
                .tooltip {
                    ...
                    position-try-fallbacks: flip-block;
                    container-type: anchored;
                }

                .tooltip::before {
                    content: '▼';
                    position: absolute;
                    top: 100%;
                }
        
                @container anchored(fallback: flip-block) {
                    .tooltip::before {
                        content: '▲';
                        top: auto;
                        bottom: 100%;
                    }
                }
            
        
            
                .tooltip {
                    ...
                    position-try-fallbacks: flip-block;
                    container-type: anchored;
                }

                .tooltip::before {
                    content: '▼';
                    position: absolute;
                    top: 100%;
                }
        
                @container anchored(fallback: flip-block) {
                    .tooltip::before {
                        content: '▲';
                        top: auto;
                        bottom: 100%;
                    }
                }
            
        
Рома Ахмадуллин: CSS Anchor Positioning

Адаптив Предпочтения пользователя

@media (prefers-color-scheme)

        
        .page {
            background: white;
            color: black;
        }
            
        @media (prefers-color-scheme: dark) {
            .page {
                background: black;
                color: white;
            }
        }
        
        
        
        .page {
            background: white;
            color: black;
        }
            
        @media (prefers-color-scheme: dark) {
            .page {
                background: black;
                color: white;
            }
        }
        
        
        
        .page {
            background: white;
            color: black;
        }
            
        @media (prefers-color-scheme: dark) {
            .page {
                background: black;
                color: white;
            }
        }
        
        

Можем учитывать не только тему

@media (пользовательские предпочтения)

@media (пользовательские предпочтения)

@media (пользовательские предпочтения)

@media (пользовательские предпочтения)

        
        selector {
           /* дефолтные стили */
        }
            
        @media (prefers-...) {
            selector {
                /* стили, учитывающие предпочтения */
            }
        }
        
        
Татьяна Фокина: CSS-медиафичи для улучшения доступности

Подытожим

Адаптив — не только про вьюпорт

Адаптив CSS-cвойства
вьюпорт @media(size)
контент flex, grid, :has()
контейнер @container, clamp()
предпочтения пользователя @media(prefers-...)
окружающая среда logical properties, anchor positioning

Важны не сами CSS-фичи

Важен контекст

Удобные интерфейсы
⬇️
наша ответственность

Веб-платформа даёт инструмент

Дело за нами

Давайте делать по-настоящему адаптивные интерфейсы!

Всем любви!

И адаптивных интефейсов :)

Рома Ахмадуллин

Лид фронтенда, Дром
@roma_akhmadullin
Про фронтенд и не только
/ Я 💛 Фронтенд 2026