Uma biblioteca leve, performática e agnóstica para simular CSS Container Queries em qualquer ambiente JavaScript.
Diferente das Media Queries tradicionais que dependem da largura da tela (viewport), esta biblioteca permite que seus componentes reajam ao tamanho do seu próprio container, garantindo modularidade real ao seu Design System.
- ⚡ Alta Performance: Utiliza
ResizeObserveraliado aorequestAnimationFramepara evitar gargalos de renderização. - 🛠 Estratégias Flexíveis: Escolha entre aplicar Atributos de Dados (padrão CSS) ou Classes CSS.
- 🧩 Escopável: Evite conflitos entre componentes distintos (ex:
.Button--mdvs.Box--md) usando prefixos customizados. - 📦 Agnóstico: Integração perfeita com qualquer framework moderno.
npm install container-media-observerA biblioteca fornece IntelliSense completo. Ao utilizar a função, seu editor de código exibirá automaticamente os tipos e descrições dos parâmetros:
/**
* @param {HTMLElement} element - O elemento DOM a ser monitorado.
* @param {Object.<string, number>} breakpoints - Ex: { sm: 300, md: 600 }
* @param {Object} [options] - Configurações opcionais.
* @param {string} [options.prefix='container-'] - Prefixo para a classe ou atributo.
* @param {'class'|'attribute'} [options.strategy='attribute'] - Onde aplicar a marcação.
* @returns {() => void} Função de limpeza (cleanup) para desconectar o observer.
*/Utilize ref para capturar o elemento e os hooks de ciclo de vida para gerenciar o observador.
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { containerJsQuery } from 'container-media-observer';
const containerRef = ref(null);
let stopQuery;
onMounted(() => {
// Aplicando estratégia de CLASSE para um Drawer
stopQuery = containerJsQuery(
containerRef.value,
{
sm: 300,
md: 700,
},
{ strategy: 'class', prefix: 'Drawer--' }
);
});
onBeforeUnmount(() => stopQuery?.());
</script>
<template>
<div ref="containerRef" class="drawer-container">
<slot />
</div>
</template>
<style scoped>
/* A classe será aplicada conforme o tamanho do container: .Drawer--sm ou .Drawer--md */
.Drawer--md {
display: flex;
padding: 2rem;
}
</style>Em Svelte, a forma mais eficiente é utilizar uma Action, que lida automaticamente com a criação e destruição do componente.
<script>
import { containerJsQuery } from 'container-media-observer';
function containerAction(node) {
const stop = containerJsQuery(
node,
{
mobile: 320,
tablet: 768,
},
{ strategy: 'class', prefix: 'widget-' }
);
return {
destroy() {
stop();
},
};
}
</script>
<div use:containerAction class="my-widget">
O estilo desta div muda conforme o tamanho dela mesma.
</div>
<style>
:global(.widget-mobile) {
font-size: 0.8rem;
}
:global(.widget-tablet) {
font-size: 1.2rem;
}
</style>Combine useRef e useEffect para inicializar a biblioteca e garantir que o cleanup seja executado ao desmontar.
import { useEffect, useRef } from 'react';
import { containerJsQuery } from 'container-media-observer';
export const Box = ({ children }) => {
const boxRef = useRef(null);
useEffect(() => {
const cleanup = containerJsQuery(
boxRef.current,
{
wide: 500,
},
{ strategy: 'class', prefix: 'Box--' }
);
return () => cleanup();
}, []);
return (
<div ref={boxRef} className="box-component">
{children}
</div>
);
};Acesse o DOM através de @ViewChild e utilize os hooks AfterViewInit e OnDestroy.
import {
Component,
ElementRef,
ViewChild,
AfterViewInit,
OnDestroy,
} from '@angular/core';
import { containerJsQuery } from 'container-media-observer';
@Component({
selector: 'app-card',
template: `<div #cardElement class="card-container">...</div>`,
})
export class CardComponent implements AfterViewInit, OnDestroy {
@ViewChild('cardElement') cardElement!: ElementRef;
private stop?: () => void;
ngAfterViewInit() {
this.stop = containerJsQuery(
this.cardElement.nativeElement,
{
full: 800,
},
{ strategy: 'attribute', prefix: 'card-' }
);
}
ngOnDestroy() {
this.stop?.();
}
}Em componentes Lit (Web Components), o método firstUpdated é o local correto para iniciar observadores de DOM.
import { LitElement, html } from 'lit';
import { query } from 'lit/decorators.js';
import { containerJsQuery } from 'container-media-observer';
class MyButton extends LitElement {
@query('.btn-wrapper') _btn;
firstUpdated() {
this._cleanup = containerJsQuery(
this._btn,
{
large: 400,
},
{ strategy: 'class', prefix: 'btn-' }
);
}
disconnectedCallback() {
super.disconnectedCallback();
this._cleanup?.();
}
render() {
return html`<div class="btn-wrapper"><button>Click Me</button></div>`;
}
}
customElements.define('my-button', MyButton);Ideal para manter uma estrutura baseada em dados:
.card[data-container-md] {
grid-template-columns: 1fr 1fr;
}Ideal para Design Systems onde cada componente tem seu próprio namespace:
/* Botão reage apenas se o container dele for 'sm' */
.Button--sm {
padding: 4px;
font-size: 10px;
}
/* Box reage apenas se o container dela for 'lg' */
.Box--lg {
padding: 40px;
border-radius: 20px;
}