Skip to content

[RFC] Descendent and Sibling selectors #536

@nmn

Description

@nmn

Describe the feature request

Disclaimer

This proposal is not "ready" or "approved". This proposal is being shared publicly to help consolidate all discussions about adding such a feature.

Motivation

StyleX has a few constraints in service of making things work consistently and generating styles that are performant and scalable. StyleX has disallowed descendent and sibling selectors to maintain it's core principles such as:

  • Deterministic styles
  • Deterministic merging of styles
  • No "styling at a distance"

However, the alternative to such selectors is to use Javascript which may often not be desirable and cause other trade-offs.

Constraints

Any design we adopt for descendent selectors must not compromise on StyleX's core principles.

We must also ensure that such a feature won't significantly affect the performance or size of the generated CSS.

Core Concept

When it comes to avoiding styling at a distance, .csuifyiu:hover > div is unsafe, but div:hover > .ksghfhjsfg is safe.

Inspiration

group and peer from Tailwind.

Proposal

This proposal would add at least two new functions to the StyleX API surface. Although the names could change, for the sake of this explainer, assume they're stylex.id and stylex.when.

The core concept is that stylex.when can be used toe read the state of an element targeted with stylex.id and apply styles conditionally. stylex.when would be allowed to be used where pseudo classes are allowed today.

stylex.id would have the same constraints as stylex.defineVars.

// target.stylex.ts
import * as stylex from '@stylexjs/stylex';
export const myId = stylex.id();
import {create, props, when} from '@stylexjs/stylex';
import {myId} from './target.stylex';

const styles = create({
  button: {
    color: {
      default: 'black',
      ':hover': 'red',
      
      [when.ancestor(myId, ':hover')]: 'blue',
      // .a-group:hover .xjygtsyrt
      [when.peerBefore(myId, ':hover')]: 'blue',
      // .a-group:hover + .xjygtsyrt
      [when.peerAfter(myId, ':hover')]: 'blue',
      // .xjygtsyrt:has(+ .a-group:hover)
      [when.descendent(myId, ':hover')]: 'blue',
      // .xjygtsyrt:has( .a-group:hover)
    },
  }
});

<Card {...props(styles.card, ...,  myId)}>
  <button {...props(styles.button} />
</Card>

Additionally, stylex.id.default should be available as a "default" id that can be used instead of creating ids manually.

Concerns

CSS Bloat

Adding this capability can lead to CSS becoming bloated.

One way to minimise bloat would be disallow creating custom ids and enforce that the same id is used for all use-cases. This would cause the same constraints as group and peer in Tailwind where there can only be a single "layer" of combinator styles at a time. This should be sufficient in the vast majority of use-cases and we can suggest using JS for the edge-cases

API Bloat

This API makes StyleX harder to learn and increases the API surface area.

Naming Bikeshed

The naming of the APIs is up for debate.

Feedback

How important is this feature?

Is the ability to create multiple custom IDs for targeting in styles important?

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions