DEV Community

Cover image for Why container queries stop matching when you switch container-type dynamically?
Chris Rodriguez
Chris Rodriguez

Posted on Originally published at chriseugenerodriguez.com

Why container queries stop matching when you switch container-type dynamically?

When a component flips classes at runtime, a container query can stop matching even when the DOM and sizes look fine. The root cause is often containment eligibility: the browser first finds an eligible container ancestor, then checks the axis or name your query uses. If the container’s container-type no longer supports the axis you ask for, there is simply no valid target and the query fails.

Implementation

  • Decide which axis your queries use. Width-based checks use the inline axis; height-based checks use the block axis. Use container-type: size when you need both axes, or container-type: inline-size when you only ever need inline-axis queries.
  • If you need to target a specific wrapper to avoid accidental matches, use container-name. Remember that naming preserves which container the browser will consider, but it does not add axis support.
  • When refactoring or toggling classes, inspect the container ancestor at the exact moment of the change: is there still a qualifying container, does it have the expected name (if you rely on names), and does it support the axis you query?

Expected behavior

  • With container-type: size, both inline (width) and block (height) queries can match the same container.
  • With container-type: inline-size, inline-axis queries continue to work but block-axis queries will report no eligible container.
  • Name-only queries can still resolve a named container regardless of axis, but axis-based checks still require the appropriate container-type.

Limitations

  • This diagnosis demonstrates container eligibility logic. It does not replace checking your real page for selector specificity, layout overrides, or browser rendering differences.
  • If a query fails because the container is too small, or because the element moved to a different ancestor chain, the correct fix is not always changing container-type.

The article includes a compact deterministic fixture that reproduces the toggle, shows the failing state, and demonstrates both repairs: keep an appropriate container-type for axis queries or use container naming for targeting problems. Use the fixture’s diagnostics to separate container discovery issues from axis-eligibility problems.

Working example

export function createElement({ tag = 'div', width = 0, height = 0 } = {}) {
  return {
    tag,
    width,
    height,
    parent: null,
    children: [],
    containerType: 'normal',
    containerName: undefined,
  };
}

export function appendChild(parent, child) {
  child.parent = parent;
  parent.children.push(child);
}

export function setContainerType(el, type) {
  el.containerType = type;
}

export function setContainerName(el, name) {
  el.containerName = name;
}

export function toggleContainerType(el, nextType) {
  setContainerType(el, nextType);
}

function compare(op, left, right) {
  switch (op) {
    case '>': return left > right;
    case '>=': return left >= right;
    case '<': return left < right;
    case '<=': return left <= right;
    default: throw new Error(`unsupported compare operator: ${op}`);
  }
}

function queryAxis(query) {
  if (query.kind === 'name-only') return null;
  if (query.axis) return query.axis;
  if (query.feature === 'width') return 'inline';
  if (query.feature === 'height') return 'block';
  return null;
}

export function findEligibleContainer(startEl, query) {
  let node = startEl.parent;
  const axis = queryAxis(query);

  while (node) {
    if (query.kind === 'name-only') {
      if (node.containerName === query.name) return node;
    } else if (axis) {
      if (node.containerType === 'size') return node;
      if (node.containerType === 'inline-size' && axis === 'inline') return node;
    }
    node = node.parent;
  }

  return null;
}

export function evaluateQuery(targetEl, query) {
  const container = findEligibleContainer(targetEl, query);
  if (!container) {
    return { matched: false, reason: 'no-eligible-container', container: null };
  }

  if (query.kind === 'name-only') {
    return { matched: true, container };
  }

  const axis = queryAxis(query);
  const size = axis === 'inline' ? container.width : container.height;
  return {
    matched: compare(query.compare, size, query.value),
    container,
    containerValue: size,
  };
}
Enter fullscreen mode Exit fullscreen mode

Read the full article: https://chriseugenerodriguez.com/blog/why-do-container-queries-stop-matching-when-you-switch-container-type-dynamically

Top comments (0)