Skip to content

Element relationships

Style an element from another element's state, size, or position through refs that TypeScript checks.

You want to style an element based on another element: a parent’s hover state, a sibling’s state, the width of a container, or the position of an anchor. Tailwind connects the two elements by name, such as group/card and group-hover/card:. It reads these names as strings, so it does not check that they match. Nested groups or containers may also need names to target the right ancestor. You must avoid name collisions when nesting components.

In Stillwind, you create the name as a variable with style.ref(). A misspelled ref is an undefined variable, so TypeScript reports it.

React to a parent’s state

Create a ref, attach it to the parent, and use it in the child’s style:

import { style } from "stillwind/react";

const card = style.ref("card");

export function Card() {
  return (
    <article {...style(card, "rounded-lg p-4")}>
      <h2 {...style({ [card("hover")]: "text-blue-600" })}>Project</h2>
    </article>
  );
}

style(card, ...) attaches the ref to the <article>. The key card("hover") applies text-blue-600 while the <article> is hovered. Write the variants inside the call, such as card("hover:active"), to combine states.

The label "card" is optional and only helps you read the generated class names. The compiler creates a unique name for each style.ref() call, so two components can use the label "card" without affecting each other.

React to a sibling’s state

Without .in(), the ref must be on an ancestor of the styled element. To use a sibling, add .in() with a ref on an element that contains both:

import { style } from "stillwind/react";

const card = style.ref("card");
const trigger = style.ref("trigger");

export function Card() {
  return (
    <section {...style(card, "p-4")}>
      <button {...style(trigger, "rounded-md px-4 py-2")}>Details</button>
      <p {...style({ [trigger("hover").in(card)]: "text-blue-600" })}>
        This text changes color while the button is hovered.
      </p>
    </section>
  );
}

trigger("hover").in(card) matches while the <button> is hovered and both the <button> and the <p> are inside the <section>.

React to a container’s size

Make the element a container with asContainer(), and query it with at():

import { style } from "stillwind/react";

const card = style.ref("card");

export function Card() {
  return (
    <section {...style(card, card.asContainer("inline-size"), "p-4")}>
      <p {...style({ [card.at("width >= 40rem")]: "text-lg" })}>
        This text grows when the card is at least 40rem wide.
      </p>
    </section>
  );
}

Pass card and card.asContainer() in the same style() call. The container applies only to an element that has the ref, and the CLI warns when the ref is missing.

Position an element at an anchor

Make an element an anchor with asAnchor(), and position another element against it with positionAnchor():

import { style } from "stillwind/react";

const trigger = style.ref("trigger");

export function AnchoredHint() {
  return (
    <section {...style(style.anchorScope([trigger]))}>
      <button {...style(trigger, trigger.asAnchor())}>Open hint</button>
      <div
        {...style(trigger.positionAnchor(), "absolute top-[anchor(bottom)] left-[anchor(left)]")}
      >
        Anchor-positioned content
      </div>
    </section>
  );
}

style.anchorScope([trigger]) limits the anchor name to the <section>, so each rendered AnchoredHint positions against its own button. Stillwind generates CSS anchor positioning only. Check that the browsers you support implement it.

Share refs between components

Export a ref from its module and import it where you need it. Keep the call or .in() chain, such as card("hover"), directly in the key of the style() call. You cannot store it in a variable and use it later.

Report group and peer variants

The stylistic preset includes prefer-typed-relationships. The rule reports group-* and peer-* variants so that you can replace them with refs.

The style.ref() reference lists every method and its accepted inputs.