# Presets úteis de @custom-media

> Presets de @custom-media para tipo de entrada, breakpoints e HDR, prontos para qualquer projeto, com um plano B em style queries enquanto o suporte não chega.

URL: https://dpw.dev/css/presets-custom-media/  
Publicado em: 2026-11-12  
Categoria: css  
Tags: web-design-responsivo, boas-praticas  
Autor: Tárcio Zemel

Com **`@custom-media`**, uma media query ganha um nome e passa a ser reutilizada como se fosse uma variável. A seguir, presets prontos para detectar o tipo de entrada do dispositivo, montar breakpoints e reconhecer telas HDR, além de um plano B com style queries que já funciona enquanto o suporte não chega.

> Artigo baseado em [Useful @custom-media Presets](https://blog.master.dev/useful-custom-media-presets/).

Em fevereiro de 2026, o Firefox 148 lançou a at-rule `@custom-media` (por enquanto, é preciso ativar a flag `layout.css.custom-media.enabled`), que serve para criar media queries personalizadas.

Como dá para imaginar, as custom media queries são como as <a href="/css/variaveis-css-guia-pratico/" target="_blank">custom properties</a> (ou "variáveis CSS"), só que para <a href="https://blog.master.dev/learn-media-queries/" target="_blank" rel="noopener noreferrer">media queries</a>. Elas permitem reaproveitar uma media query pelo nome que demos a ela.

![Comparação desenhada à mão: uma custom property --brand guarda uma cor usada em var(--brand), e um @custom-media --touch guarda a condição (hover: none) and (pointer: coarse), usada em @media (--touch)](/images/blog/presets-custom-media/nome-para-media-query.webp)

Alguns exemplos para dar uma ideia de como funcionam:

```css
/* Definição básica */
@custom-media --no-hover (hover: none);
@custom-media --landscape (orientation: landscape);

/* Definição combinada */
@custom-media --no-hover-landscape (hover: none) and (orientation: landscape);
@custom-media --no-hover-landscape (--no-hover) and (--landscape);
@custom-media --no-hover-landscape (hover: none) and (--landscape);

/* Uso básico */
@media (--no-hover) { /* ... */ }
@media (--landscape) { /* ... */ }
@media (--no-hover-landscape) { /* ... */ }

/* Uso combinado */
@media (--no-hover) and (--landscape) { /* ... */ }
@media (--no-hover) and (orientation: landscape) { /* ... */ }
@media (hover: none) and (--landscape) { /* ... */ }
```

Os tipos de mídia (como `all`, `screen` e `print`), os operadores lógicos (`not`, `and`, `only` e a sintaxe de "ou" separada por vírgulas) e todas as outras sintaxes de media query podem ser usados com custom media queries.

Este artigo reúne alguns presets úteis de `@custom-media` que podem entrar em qualquer projeto sem impor muitas opiniões. Antes, porém, é preciso entender o suporte nos navegadores e as opções que existem hoje.

## Suporte nos navegadores

- O Firefox tem uma flag: `layout.css.custom-media.enabled`
- O Chrome tem um <a href="https://www.mail-archive.com/blink-dev@chromium.org/msg14281.html" target="_blank" rel="noopener noreferrer">Intent to Prototype</a>, mas nada andou desde então.
- O Safari <a href="https://github.com/WebKit/standards-positions/issues/330" target="_blank" rel="noopener noreferrer">ainda não tem nada</a>.

Ou seja, ainda é cedo.

Como `@custom-media` ainda não funciona em todos os principais navegadores, os presets também aparecem aqui numa sintaxe alternativa que já tem suporte: a das <a href="/css/style-queries/" target="_blank">container style queries</a>.

A sintaxe também pode ser "processada": existe <a href="https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media" target="_blank" rel="noopener noreferrer">um plugin do PostCSS</a> que <a href="https://codepen.io/editor/chriscoyier/pen/01a17d52-21f4-7bdf-8cd4-0a2a9916d09e" target="_blank" rel="noopener noreferrer">dá conta do recado</a> para quem quer se preparar para o futuro.

## Queries `@custom-media` para capacidades do dispositivo

O tamanho da tela não indica mais a capacidade do dispositivo, se é que algum dia indicou.

Claro, antigamente bastavam três breakpoints para saber se o aparelho era um celular, um tablet ou um desktop. Hoje, nem dá para ter certeza do que esses termos significam: alguns "celulares" e "tablets" aceitam mouse e teclado, enquanto alguns "desktops" têm tela de toque e até suporte a caneta.

Breakpoints continuam úteis, mas não dizem nada sobre as capacidades do dispositivo.

O que precisamos descobrir é o mecanismo de entrada principal, para atender ao que o usuário provavelmente está usando. Isso exige uma combinação de media queries (as mesmas que servem para <a href="/css/como-detectar-touch-com-css-puro/" target="_blank">detectar touch com CSS puro</a>), e `@custom-media` é perfeita para isso:

```css
/* Mecanismo de entrada principal é mouse ou algo que emula mouse */
@custom-media --mouse (hover: hover) and (pointer: fine);

/* Mecanismo principal é toque */
@custom-media --touch (hover: none) and (pointer: coarse);

/* Caneta (stylus) */
@custom-media --stylus (hover: none) and (pointer: fine);

/* Air mouse, caneta aérea ou controle remoto com ponteiro */
@custom-media --air (hover: hover) and (pointer: coarse);

/* Gamepad ou controle remoto com botões direcionais */
@custom-media --dpad (hover: none) and (pointer: none);
```

![Tabela desenhada à mão cruzando hover (hover, none) e pointer (fine, coarse, none): --mouse em hover e fine, --air em hover e coarse, --stylus em none e fine, --touch em none e coarse, --dpad em none e none, e a célula de hover com pointer none marcada como sem uso prático](/images/blog/presets-custom-media/hover-pointer-tipos-de-entrada.webp)

Só que, para criar boas experiências em todos os casos, também é preciso levar em conta os mecanismos de entrada secundários.

Infelizmente, não existe um jeito definitivo de saber qual deles o usuário está de fato usando quando há vários disponíveis. Dois exemplos:

- Um mouse conectado de propósito a um tablet (o usuário provavelmente está usando o mouse secundário, mas não há como confirmar)
- Um desktop com tela de toque (o usuário provavelmente usa o mouse principal, mas os dois vêm embutidos, então vai saber?)

Dá para ver como isso pode complicar, certo? Mesmo usando a família `any-` de media features (por exemplo, `any-hover`), elas dizem se *algum* dos mecanismos de entrada tem aquela capacidade, e não se o mecanismo secundário em particular a tem.

Mas isso resolve, não? Não. Veja isto:

```css
@custom-media --mouse (any-hover: hover) and (any-pointer: fine);
```

Isso não pergunta se algum dos mecanismos de entrada é um mouse. Pergunta se um mecanismo de entrada é mouse ou ponteiro aéreo (com `any-hover: hover`) e se um mecanismo (talvez o mesmo) é mouse ou caneta (com `any-pointer: fine`).

A combinação a seguir não é muito melhor. Ela significa que o mecanismo de entrada principal é mouse ou ponteiro aéreo, enquanto algum mecanismo é mouse ou caneta.

```css
@custom-media --mouse (hover: hover) and (any-pointer: fine);
```

O único jeito de ter certeza é ser ultraespecífico, mas será que queremos administrar um caminhão de custom media queries? Provavelmente não. Por isso, é melhor focar no mecanismo de entrada principal e garantir que o site funcione razoavelmente bem com os outros mecanismos desde o início.

Isso não significa que casos extremos não vão aparecer, apenas que pode valer a pena tratar as media queries `any-` como complementos. Assim dá para atingir mecanismos de entrada secundários quando necessário, sem criar custom media queries demais:

```css
@media (--touch) and (any-hover: hover) {
  /* Dispositivo de toque + capacidade de hover (por exemplo, iPad com mouse) */
}

@media (--touch) and (any-pointer: fine) {
  /* Dispositivo de toque + ponteiro preciso (por exemplo, iPad com mouse ou caneta) */
}

@media (--mouse) and (any-pointer: coarse) {
  /* Dispositivo com mouse + capacidade de toque (por exemplo, Microsoft Surface) */
}

@media (--stylus) and (any-pointer: coarse) {
  /* Dispositivo com caneta + capacidade de toque (por exemplo, mesa digitalizadora) */
}
```

Lembre-se: as custom media queries ainda não funcionam em todos os navegadores. Por isso, aqui estão os mesmos presets na sintaxe de container style queries, que funciona em todos eles:

```css
/* Definição */
:root {
  @media (hover: hover) and (pointer: fine) {
    --mouse: any-valid-ident;
  }

  @media (hover: none) and (pointer: coarse) {
    --touch: true;
  }

  @media (hover: none) and (pointer: fine) {
    --stylus: true;
  }

  @media (hover: hover) and (pointer: coarse) {
    --air: true;
  }

  @media (hover: none) and (pointer: none) {
    --dpad: true;
  }
}

/* Uso */
@container style(--mouse) { /* ... */ }
@container style(--touch) { /* ... */ }
@container style(--stylus) { /* ... */ }
@container style(--air) { /* ... */ }
@container style(--dpad) { /* ... */ }
```

Uma pequena ressalva: container style queries não servem para estilizar o `:root`, porque ele é o elemento de nível mais alto e não tem nenhum container acima dele.

Uma ressalva menor ainda: container queries não podem ser combinadas com media queries numa mesma condição. Seria preciso transformar todas elas em custom properties, assim:

```css
:root {
  @media (hover: none) and (pointer: coarse) {
    --touch: true;
  }

  @media (any-hover: hover) {
    --any-hover-hover: true;
  }
}

@container style(--touch) and style(--any-hover-hover) {
  /* Dispositivo de toque + capacidade de hover (por exemplo, iPad com mouse) */
}
```

Essa saída não agrada nem um pouco. De novo, são custom properties demais para administrar, ainda mais quando dá simplesmente para aninhar:

```css
@container style(--touch) {
  @media (any-hover: hover) {
    /* Dispositivo de toque + capacidade de hover (por exemplo, iPad com mouse) */
  }
}
```

![Esquema desenhado à mão do plano B: o :root grava --touch: true dentro de uma media query, os descendentes herdam e consultam com @container style(--touch), e duas notas lembram que o :root não pode ser estilizado assim e que style() e @media devem ser aninhados](/images/blog/presets-custom-media/fallback-style-queries.webp)

## Queries `@custom-media` para breakpoints

Os tamanhos de tela variam bastante hoje em dia, então, de novo, não é tão simples quanto ter breakpoints para celular, tablet e desktop. A estratégia sugerida é "maior ou igual a [este] tamanho", em incrementos de `100px`.

Tomando <a href="https://gs.statcounter.com/screen-resolution-stats" target="_blank" rel="noopener noreferrer">estas estatísticas de resolução de tela</a> como guia, a faixa atual vai de `360px` a `1560px`. As custom media queries para isso ficam assim (com a <a href="/css/css-range-syntax-media-queries/" target="_blank">range syntax das media queries</a>, que troca `min-width` por `width >=`):

```css
/* Definição */
@custom-media --from-360 (width >= 360px);
@custom-media --from-460 (width >= 460px);
@custom-media --from-560 (width >= 560px);
@custom-media --from-660 (width >= 660px);
@custom-media --from-760 (width >= 760px);
@custom-media --from-860 (width >= 860px);
@custom-media --from-960 (width >= 960px);
@custom-media --from-1060 (width >= 1060px);
@custom-media --from-1160 (width >= 1160px);
@custom-media --from-1260 (width >= 1260px);
@custom-media --from-1360 (width >= 1360px);
@custom-media --from-1460 (width >= 1460px);
@custom-media --from-1560 (width >= 1560px);

/* Uso */
@media (--from-360) { /* ... */ }
@media (--from-460) { /* ... */ }
@media (--from-560) { /* ... */ }
@media (--from-660) { /* ... */ }
@media (--from-760) { /* ... */ }
@media (--from-860) { /* ... */ }
@media (--from-960) { /* ... */ }
@media (--from-1060) { /* ... */ }
@media (--from-1160) { /* ... */ }
@media (--from-1260) { /* ... */ }
@media (--from-1360) { /* ... */ }
@media (--from-1460) { /* ... */ }
@media (--from-1560) { /* ... */ }
```

Aqui estão eles na sintaxe de container style queries:

```css
/* Definição */
:root {
  @media (width >= 360px) {
    --from-360: true;
  }

  @media (width >= 460px) {
    --from-460: true;
  }

  @media (width >= 560px) {
    --from-560: true;
  }

  @media (width >= 660px) {
    --from-660: true;
  }

  @media (width >= 760px) {
    --from-760: true;
  }

  @media (width >= 860px) {
    --from-860: true;
  }

  @media (width >= 960px) {
    --from-960: true;
  }

  @media (width >= 1060px) {
    --from-1060: true;
  }

  @media (width >= 1160px) {
    --from-1160: true;
  }

  @media (width >= 1260px) {
    --from-1260: true;
  }

  @media (width >= 1360px) {
    --from-1360: true;
  }

  @media (width >= 1460px) {
    --from-1460: true;
  }

  @media (width >= 1560px) {
    --from-1560: true;
  }
}

/* Uso */
@container style(--from-360) { /* ... */ }
@container style(--from-460) { /* ... */ }
@container style(--from-560) { /* ... */ }
@container style(--from-660) { /* ... */ }
@container style(--from-760) { /* ... */ }
@container style(--from-860) { /* ... */ }
@container style(--from-960) { /* ... */ }
@container style(--from-1060) { /* ... */ }
@container style(--from-1160) { /* ... */ }
@container style(--from-1260) { /* ... */ }
@container style(--from-1360) { /* ... */ }
@container style(--from-1460) { /* ... */ }
@container style(--from-1560) { /* ... */ }
```

Breakpoints do tipo "menor que" raramente são necessários nessa estratégia, mas são fáceis de usar junto com os de "maior que" quando preciso:

```css
/* Definição */
@custom-media --from-360 (width >= 360px);
@custom-media --to-659 (width <= 659px);

/* Uso (flexível) */
@media (--from-360) { /* Maior ou igual a */ }
@media (--to-659) { /* Menor ou igual a */ }
@media (--from-360) and (--to-659) { /* Entre os dois */ }
```

Para encurtar, também dá para expressar breakpoints intermediários numa única condição de media query, mas ao custo da flexibilidade. Imagine quantas variações disto seriam necessárias:

```css
/* Definição */
@custom-media --from-360-to-659 (360px <= width <= 659px);

/* Uso (rígido) */
@media (--from-360-to-659) { /* ... */ }
```

Esta seção é um pouco mais subjetiva que as outras, então fique à vontade para incluir `height`, `orientation` ou outra coisa, se usar com frequência suficiente para justificar mais custom media queries.

Talvez também valha adotar uma convenção de nomes (por exemplo, `--xsm`/`--sm`/`--md`/`--lg`/`--xlg`/etc.), mas essa escolha fica com você.

## Uma query `@custom-media` para detectar HDR de verdade

HDR de verdade exige uma tela com alta faixa dinâmica e gama de cores ampla. Sem isso, imagens e vídeos HDR podem ficar bem feios. O resultado é brilho alto com cores estranhamente dessaturadas, cores vibrantes estranhamente escuras, ou as duas coisas.

Então, para saber se dá ou não para usar imagens e vídeos HDR de verdade, deixe esta custom media query à mão:

```css
/* Definição */
@custom-media --true-hdr (dynamic-range: high) and (color-gamut: p3);

/* Uso */
@media (--true-hdr) { /* ... */ }
```

E aqui está ela na sintaxe de container style queries:

```css
/* Definição */
:root {
  @media (dynamic-range: high) and (color-gamut: p3) {
    --true-hdr: true;
  }
}

/* Uso */
@container style(--true-hdr) { /* ... */ }
```

## No fim das contas

A expectativa é grande para usar custom media queries, mas, enquanto isso, as container style queries servem bem como alternativa.

Uma grande vantagem de brincar com elas desde já é perceber que não são tão diferentes das custom properties quando se trata de equilibrar concisão e flexibilidade. Leva um tempo para descobrir como organizá-las bem.

Quando todos os navegadores suportarem `@custom-media`, o autor, Daniel Schwarz, pretende colocar estes presets no seu CSS reset. Ele também quer saber se mais alguém tem ideias, ou mesmo opiniões sobre como `@custom-media` funciona. O <a href="https://github.com/argyleink/open-props/blob/main/src/props.media.css" target="_blank" rel="noopener noreferrer">Open Props tem os seus próprios presets</a>. É uma boa ideia? Você vai usar?

## Perguntas frequentes

### Dá para escrever `@custom-media` hoje e deixar uma ferramenta de build resolver?

Dá. O [plugin postcss-custom-media](https://github.com/csstools/postcss-plugins/tree/main/plugins/postcss-custom-media) troca cada `@media (--nome)` pela media query completa, e o [Lightning CSS faz a mesma substituição](https://lightningcss.dev/transpilation.html) com a opção `drafts.customMedia`. Um detalhe do PostCSS: cada arquivo é processado separadamente, então definições que moram em outro arquivo precisam do `@csstools/postcss-global-data` para serem encontradas.

### O plano B com container style queries funciona em qualquer navegador?

Nos atuais, sim, mas o suporte é recente. Style queries com custom properties chegaram ao Chrome 111 e ao Safari 18, e o Firefox só entrou na versão 151, o que as tornou [Baseline recentemente disponível](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@container#browser_compatibility) em maio de 2026. Um navegador mais antigo ignora o bloco `@container style()` inteiro, então o estilo base, fora da query, precisa funcionar sozinho.

### O que acontece se duas regras `@custom-media` definirem o mesmo nome?

Vale a regra que está em vigor no momento em que o `@media` é avaliado, e uma redefinição posterior não muda as referências anteriores, [segundo o MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@custom-media). O próprio MDN avisa que esse comportamento ainda está em discussão na especificação. Enquanto isso, o mais seguro é definir cada nome uma vez só, de preferência num arquivo de presets carregado antes do resto.

### Posso usar `true` ou `false` como valor de um `@custom-media`?

Pode. A sintaxe aceita [as palavras-chave `true` e `false`](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@custom-media) no lugar de uma media query: `@custom-media --debug true;` faz todo `@media (--debug)` valer sempre, e `false` desliga o bloco. É um jeito prático de ligar e desligar um conjunto de regras durante o desenvolvimento sem apagar nada.

## Teste seus conhecimentos

**Pergunta 1.** Num notebook com tela de toque, o usuário só usa o touchpad. O que esta condição revela?

```css
@custom-media --mouse (any-hover: hover) and (any-pointer: fine);
```

- Que o mecanismo principal do aparelho é um mouse
  As features `any-` não falam do mecanismo principal. Para isso existem `hover` e `pointer`, sem o prefixo.
- Que algum mecanismo tem hover e algum tem ponteiro preciso **(correta)**
  Cada `any-` olha para todos os mecanismos, separadamente. Pode ser o mesmo dispositivo nas duas condições ou dois diferentes.
- Qual dos mecanismos o usuário está usando agora
  Nenhuma media query sabe disso. Com vários mecanismos disponíveis, não há como confirmar qual está em uso.

Revela só que algum mecanismo tem hover e algum tem ponteiro preciso. `any-hover` e `any-pointer` são avaliadas cada uma sobre todos os mecanismos, então a condição não garante que exista um mouse, nem diz qual entrada está em uso. Por isso o post recomenda focar no mecanismo principal e usar as `any-` como complemento.

**Pergunta 2.** O `:root` ganha `--touch: true` dentro de uma media query. O que acontece com este CSS num celular?

```css
@container style(--touch) {
  :root {
    font-size: 1.125rem;
  }
}
```

- A fonte da raiz aumenta, porque `--touch` está definida
  A propriedade está lá, mas a query precisa de um container acima do elemento estilizado, e o `:root` não tem nenhum.
- Nada muda: o `:root` não tem um container acima dele **(correta)**
  Uma container query estiliza descendentes do container. O `:root` é o topo da árvore, então não há quem consultar.
- Só funciona se o `:root` tiver `container-type`
  Style queries não exigem `container-type`. O problema é outro: nenhum elemento fica acima do `:root`.

Nada muda. Container style queries não estilizam o `:root`, porque ele é o elemento de nível mais alto e não tem container acima. O que precisa mudar na raiz continua dentro de uma `@media` comum.

**Pergunta 3.** Qual é o jeito sugerido de aplicar estilos só a dispositivos de toque que também têm hover, usando o plano B com style queries?

- `@container style(--touch) and (any-hover: hover)`
  Container queries não se combinam com media queries numa mesma condição, então essa regra não vale.
- Uma custom property para cada media feature, como `--any-hover-hover`
  Funciona, mas multiplica as custom properties a administrar. Foi justamente a saída descartada no post.
- Aninhar `@media (any-hover: hover)` dentro de `@container style(--touch)` **(correta)**
  O aninhamento combina as duas consultas sem criar uma custom property para cada media feature.

Aninhar. Como `style()` e `@media` não entram na mesma condição, a saída mais enxuta é colocar a media query dentro da container query, em vez de transformar cada media feature numa custom property.

**Pergunta 4.** Um projeto precisa de estilos para vários intervalos de largura (de 360 a 659, de 460 a 959 e assim por diante). Que definição escala melhor?

- Um `--from-*` e um `--to-*` por breakpoint, combinados com `and` no uso **(correta)**
  Cada limite é definido uma vez e qualquer intervalo sai da combinação de dois nomes, como `(--from-360) and (--to-659)`.
- Um nome por intervalo, como `--from-360-to-659`
  É mais curto no uso, mas rígido: cada par de limites vira uma definição nova, e as variações se multiplicam.
- Só nomes `--from-*`, porque intervalos não cabem em `@custom-media`
  Cabem: a range syntax aceita `(360px <= width <= 659px)`. A questão é quantas definições isso exige.

Um `--from-*` e um `--to-*` por breakpoint. Definir o intervalo inteiro num nome só encurta o uso, mas exige uma definição para cada combinação de limites, enquanto os limites separados cobrem qualquer intervalo com `and`.

---

Fonte: [dpw - desenvolvimento para web](https://dpw.dev/) (pt-BR).
Guia para agentes: https://dpw.dev/llms.txt
Índice completo: https://dpw.dev/sitemap-index.xml
Feed: https://dpw.dev/rss.xml
