Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use CSS4 :has() selector (when available in browsers) #1

Open
parasyte opened this issue Jul 3, 2021 · 2 comments · May be fixed by #3
Open

Use CSS4 :has() selector (when available in browsers) #1

parasyte opened this issue Jul 3, 2021 · 2 comments · May be fixed by #3
Labels
enhancement New feature or request

Comments

@parasyte
Copy link
Owner

parasyte commented Jul 3, 2021

The filter buttons currently work by setting a data attribute on the main element in JavaScript:

MAIN.dataset.filter = $('input[name="filter"]:checked').value;

This was necessary because, as far as I can tell, there is currently no way to do this filtering in CSS alone.

CSS selector syntax operates on a node hierarchy. So it is possible to do this kind of filtering when an ancestor has a specific attribute:

task-webapp/index.css

Lines 96 to 104 in efe6801

/*
Hide filtered tasks and the radio buttons.
Again, only CSS is necessary because the DOM knows the state of the contents it owns.
*/
input[name='filter'],
main[data-filter='active'] task-item.complete,
main[data-filter='complete'] task-item:not(.complete) {
display: none;
}

Selecting elements based on properties of siblings is also possible. E.g. a DOM with this structure:

<task-list>
  <input type="radio" name="filter" value="all" id="filter-all" checked>
  <input type="radio" name="filter" value="active" id="filter-active">
  <input type="radio" name="filter" value="complete" id="filter-complete">
  <task-item></task-item>
</task-list>

Where <task-item> elements are appended to <task-list> can do the filtering with the following CSS:

#filter-active:checked ~ task-item.complete,
#filter-complete:checked ~ task-item:not(.complete) {
  display: none;
}

The major drawback is that we can no longer style the <label> elements, which are not siblings of the <input> elements in the above structure. Moving the labels into the <task-list> also does not make sense. Arguably, the <input> elements do not belong there, either. But because we are hiding them, it's only structurally inconvenient.

Being unable to style the labels is a showstopper for filtering with pure CSS for now.

CSS4 defines a :has() selector, which can select an element based on properties of its descendants. A document structure like we have today can implement filtering with it, without depending on JavaScript:

<main>
  <task-list>
    <task-item></task-item>
  </task-list>
  <fieldset>
    <input type="radio" value="all" id="filter-all"><label for="filter-all">All</label>
    <input type="radio" value="active" id="filter-active"><label for="filter-active">Active</label>
    <input type="radio" value="complete" id="filter-complete"><label for="filter-complete">Complete</label>
  </fieldset>
</main>
main:has(#filter-active:checked) task-item.complete,
main:has(#filter-complete:checked) task-item:not(.complete) {
  display: none;
}

This bit of magic lets the selector for main reach into the <fieldset> to find which radio button is checked. The fieldset and radio buttons live in a different tree from the <task-item> elements that we want to hide. It allows our document structure to remain structurally sound.

The :has() selector currently has only experimental prototype support in Chrome. It is not usable today.

@parasyte
Copy link
Owner Author

https://caniuse.com/css-has

Current status: Not supported in Firefox (behind a feature flag).

@parasyte
Copy link
Owner Author

parasyte commented May 4, 2024

Status update: :has() is now supported everywhere that matters as of April 2024. I want at minimum one year of the feature in the wild before the app depends on it.

Since Firefox 121 introduced support in December 2023, I wouldn't want to make this change until at least December 2024.

parasyte added a commit that referenced this issue May 4, 2024
The anticipated release date for this is December 2024. At that time,
the `:has()` selector will have been available in the wild for at least
1 year on all major browsers.

- Closes #1
@parasyte parasyte linked a pull request May 4, 2024 that will close this issue
@parasyte parasyte added the enhancement New feature or request label May 6, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant