Seb Dancer-Michel

Web developer –

Hi there!

I’m Seb, a web developer with 5+ years of experience making

With an affinity for front-end development and UI design, I focus my efforts on crafting adequate interfaces between humans and technology.

Discover my workDiscover my work

Read my blogRead my blog

29 years old, living in Amsterdam, Netherlands and originally from Toulouse, France. Curious about mostly everything. An optimist at heart, always tries to focus on the bright side of things. A lover of memes, cats, red pandas, and cute animals in general. A millenial in mind and spirit. Will always strive to improve our world in the smallest ways.

Seb Dancer-Michel

Solving puzzles is what I love doing most.

To find pragmatic solutions to practical problems and ideas. Whether it’s building a web page, a contact form, an interactive installation, or a hands-free display, I’m always looking to shape the adequate interface.

I have a broad horizontal knowledge of all things Internet and tech, and my curiosity pushes me to learn new things and adapt to the fast changing Web continuously.

What I use regularly: HTML, CSS, Javascript/Typescript, ReactJS, and ThreeJS.


  • 2023-now – Freelance
  • 2019-2023 – Your Majesty – full time contract
  • 2018 – Werkstatt – 6 mo. apprenticeship
  • 2017 – Your Majesty – 5 mo. internship
  • 2016 – Cinémur – 4 mo. internship
  • 2015 – Purée Maison – 3 mo. internship
  • 2014 – Oréalys – 2 mo. internship

I studied at HETIC (and got my masters degree) between 2014 and 2019, going through a multi-disciplinary program teaching us design, web development, communication, marketing, entrepreneurship, and everything else.

I was involved with the largest student movement in France, the Junior-Entreprises. I worked for 1 year at HETIC's own J.E., Synerg'hetic, then volunteered to work as part of the team of 20 students managing the French confederation on a national level.

2024-03-13RSS feedRSS feedContent filters using only CSS

Content filters using only CSS

tl;dr: a friend asked me to help adding a simple system to filter content on her website. I did it using only CSS, and leveraging the power of :has. Here's a codepen to see it in action.

The problem

A friend of mine asked me to help her adding a simple system to filter content on her website. She has many pieces of work to present, and wanted to add categories to them. She built her website using Cargo. Nothing complicated, but I wanted to do it quickly and was curious if you could do it without any dependencies.

Turns out you can! And it's damn simple.

The solution

First, let's write our HTML: we'll have a list of items, and a list of radio inputs to filter them.

<div class="filters">
    <input type="radio" name="filter" value="all" checked />
    <input type="radio" name="filter" value="branding" />
    <input type="radio" name="filter" value="motion" />
    <input type="radio" name="filter" value="webdesign" />

<ul class="items">
  <li data-tags="webdesign">one (webdesign)</li>
  <li data-tags="branding,webdesign">two (branding, webdesign)</li>
  <li data-tags="branding,motion">three (branding, motion)</li>
  <li data-tags="motion">four (motion)</li>
  <li data-tags="branding">five (branding)</li>

Nothing fancy here. You’ll note that the radio inputs all have the same name

so toggling one toggles the others. In parallel, the items all have a custom
attribute, which allows us to specify the associated tags to each item.

Now for the CSS:

We style the page and the basic elements, nothing of note here:

body {
  font-family: sans-serif;
  font-size: 20px;
  background: black;
  color: white;
  margin: 0;
  padding: 20px;

.filters {
  display: flex;
  flex-direction: column;

input:checked + span {
  text-decoration: underline;

.items {
  all: unset;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: 20px;
  margin-top: 20px;

.items li {
  aspect-ratio: 1/1;
  border: 2px solid white;
  display: flex;
  justify-content: center;
  align-items: center;

And finally, we apply the magic:

.filters:has(input[value='all']:checked) + .items li {
  display: flex;

.filters:has(input[value='branding']:checked) + .items li:not([data-tags*='branding']),
.filters:has(input[value='motion']:checked) + .items li:not([data-tags*='motion']),
.filters:has(input[value='webdesign']:checked) + .items li:not([data-tags*='webdesign']) {
  display: none;

Now if those lines look a bit cryptic, I'll translate them for you:

  1. The first rule says: "if the input with value 'all' is checked, then display all the items". It serves as a default/reset.
  2. The second rule says: "if the input with value 'branding' is checked, then display only the items that do not have the 'branding' tag" (and so on for the other inputs).

What's important to note here: We're essentially comparing the two parent elements

. It's important that the two elements being compared are at the same level. It's a simple principle, but I find it very powerful and makes the dreaded CSS Cascade working in our favour! There's really a lot of stuff that can be done using this, and I'm sure we'll see more and more of it in the future.

And here's the final result!

Nothing is perfect

Of course this is a very simplistic solution for a problem that can be a bigger headache than in my situation.

For example, no animations. This could be added with some CSS classes and a bit of Javascript. And probably some CSS View Transitions in the mix? I'd be curious to see that.

And what about that repeated last rule? It's not very DRY and not easy to maintain as is. In a real project, I would probably:

  • either make the list item a component that would write its own rules based on its tags,
  • or have a bit of Javascript to generate the rules based on the items.

Regardless, this was a fun little exercise in flexing my CSS muscles and I liked doing it, especially to help out a friend. I hope you found it interesting too! I'll try and post more of those small tricks as I come across them, so stay tuned!

See you on the Internet! 👋

Seb Dancer-Michel © 2024