Build a Product Filter with CSS

Build a CSS-only product filter with custom properties and style queries.

Introduction

30 Second Summary

A long product list makes shoppers scan every item to find what fits. Checkboxes can turn that search into an instant shortlist.

In this project, you will build a product filter using HTML and CSS. Two checkboxes will filter a product grid without JavaScript.

What You'll Build

When you open your finished shop page, you can tick Backpack or Duffel to watch unrelated products disappear from the grid.

By the end of this project, you'll have:

  • A working product grid that displays every item when no filters are selected.
  • Two checkbox filters that store true or false states in registered custom properties.
  • A no-JavaScript filtering pattern that uses container style queries to show the matching products.
  • Secret Mission: An optional challenge to push your skills further.

Are there any prerequisites?

You only need a text editor plus a current browser. This project requires no account or software installation.

Before We Start

The product filter depends on newer CSS features to store checkbox state. Those features also decide which products remain visible.

An older browser can still render the checkboxes while ignoring the filtering rules. This readiness check prevents that silent failure before you create the page.

You'll confirm that a code editor is ready. You'll then prove that your browser supports the full CSS pattern.

In this step, get ready to:
  • Prepare a plain-text code editor.
  • Confirm that your browser supports @property, :has(), and container style queries.
  • Open DevTools to inspect page structure.
Prepare your code editor

An HTML file must stay as plain text. A code editor preserves every tag exactly as you type it.

  • Press Cmd+Space on macOS or the Windows key on Windows to open system search.
  • Type the name of your code editor into the search field.
  • Press Enter to open the editor.

Good, your writing space is ready for the single file you build next.

Can't find a code editor?

  • Use an existing editor that can save plain-text files.
  • Switch a rich-text editor into its plain-text mode before continuing.

Need help choosing an available editor? Help me find a plain-text code editor on my computer.

Confirm browser support

Browser support is the key dependency for this project. Container style queries are the newest part of the pattern.

  • Switch to the browser displaying this project.
  • Open the official browser support update for container style queries.
  • Find the section about container style queries for custom properties.
  • Confirm that the support row includes your browser family.

Current major browser releases support custom-property style queries. The support row confirms that your chosen browser can run the filter.

Why check all three features?

The @property rule constrains each filter state to valid values.

The :has() selector reacts when a checkbox changes state.

An @container style() rule reads that state before showing the matching products.

Browser missing from the support row?

  • Update the browser to its current stable release.
  • Use another current major browser if updates are blocked on this device.

Still unsure about support? Help me check whether my browser supports this CSS filter pattern.

Open browser DevTools

DevTools exposes the document structure behind a page. Its style panel shows the CSS rules applied to the selected element.

Before you open DevTools, do you expect to see the visual page or its underlying structure?

Chrome, Edge, or Firefox

  • Switch back to your chosen browser.
  • Press Cmd+Option+I on macOS or Ctrl+Shift+I on Windows to open DevTools.
  • Select Elements if you use Chrome or Edge.
  • Select Inspector if you use Firefox.

You'll see the page's HTML as a DOM tree. You'll also see a panel containing its CSS rules.

Safari

  • Switch back to Safari.
  • Click Safari in the macOS menu bar.
  • Click Settings.
  • Click Advanced.
  • Select Show features for web developers.
  • Close the settings window.
  • Press Cmd+Option+I to open Web Inspector.
  • Select Elements.

You'll see the page's HTML as a DOM tree. You'll also see the CSS details for the selected element.

That's the readiness check complete. Your browser can expose the structure and CSS state you inspect throughout this project.

Your tools are ready. Next, you'll create the single HTML file that becomes your product filter.

Build the Product Grid

A filter needs controls that capture each choice. It also needs product cards with clear category labels.

In this step, you will create product-filter.html as a single HTML shop page.

You will add separate Backpack and Duffel checkboxes. Every product starts visible so later CSS changes have a clear baseline.

In this step, get ready to:
  • Create the shop document in product-filter.html.
  • Add labelled checkboxes with the IDs isBackpack and isDuffel.
  • Build the product grid with identifiable bag categories.
Create the shop page

The document shell gives the browser a valid page to display. Its empty main element provides a clear home for the controls and products.

  • Create product-filter.html on your Desktop with the new-file control in your code editor.

Your editor tab should now show product-filter.html.

  • Build the initial shop document by adding the code below to product-filter.html:
<!doctype html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Product Filter</title>
</head>
<body>
  <!-- Introduce the shop before the interactive controls. -->
  <header>
    <h1>Trail Supply Shop</h1>
    <p>Choose a bag type to narrow the product list.</p>
  </header>

  <!-- Keep the controls and products in the main page region. -->
  <main>
  </main>
</body>
</html>

What Does This Page Shell Do?

  • The document metadata helps the browser size and interpret the page correctly.
  • The header tells shoppers what the page contains.
  • The main element separates the interactive shop content from the introduction.
  • Save product-filter.html.
  • Open product-filter.html in your browser using the method you practised earlier.

You should see the Trail Supply Shop heading with one sentence underneath it. Your page shell is now loading as a real browser document.

Page Blank or Showing Code?

Confirm that the filename ends with .html. A text-file extension can make the browser display the source instead of the page.

Check that every opening tag has the matching closing tag shown above.

Help me fix my HTML page

Add the filter controls

Each label connects visible text to one checkbox. The IDs give future style rules stable hooks for reading the selected filters.

  • Find the empty main element in product-filter.html.
  • Add the filter form between its opening and closing tags by pasting this code:
    <!-- Group the controls so each label toggles its checkbox. -->
    <form class="filters">
      <fieldset>
        <legend>Filter by type</legend>
        <label for="isBackpack">
          <input type="checkbox" id="isBackpack">
          Backpack
        </label>
        <label for="isDuffel">
          <input type="checkbox" id="isDuffel">
          Duffel
        </label>
      </fieldset>
    </form>

How Do These Controls Work?

  • The fieldset groups both checkboxes as one filter choice area.
  • Each label uses a for value that matches its checkbox ID.
  • The IDs isBackpack and isDuffel let later style rules identify the checked control.
  • Save product-filter.html.
  • Refresh the page in your browser.

You should see a Filter by type group containing separate Backpack and Duffel checkboxes. Clicking either label should toggle its checkbox.

Checkbox Label Not Responding?

Compare each label's for value with the matching input's id. The two values must use the same spelling and capitalisation.

Help me connect my labels and checkboxes

Build the product grid

Category classes make each card readable to future filter rules. The Convertible Travel Bag belongs to both categories so combined filters have a visible match.

  • Find the closing form tag inside main.
  • Add the product section directly below that tag by pasting this code:
    <!-- Classes identify which filter state each product matches. -->
    <section class="product-grid" aria-label="Products">
      <article class="product-card backpack">
        <h2>Summit Backpack</h2>
        <p>A compact backpack for day trips.</p>
      </article>

      <article class="product-card duffel">
        <h2>Weekender Duffel</h2>
        <p>A roomy duffel for short breaks.</p>
      </article>

      <article class="product-card backpack duffel">
        <h2>Convertible Travel Bag</h2>
        <p>A flexible bag that works both ways.</p>
      </article>
    </section>

How Are the Products Identified?

  • Every article has the shared product-card class so one rule can target all products.
  • The backpack class marks products that match the Backpack filter.
  • The duffel class marks products that match the Duffel filter.
  • The Convertible Travel Bag carries both category classes because it matches both choices.
  • Save product-filter.html.

Before you refresh, pause on this prediction: how many product cards will the unfiltered page show.

  • Refresh product-filter.html in your browser.

You should see Summit Backpack, Weekender Duffel, and Convertible Travel Bag beneath the checkboxes. All three cards remain visible when you tick either checkbox because the filtering styles have not been added yet.

Missing a Product Card?

Confirm that the product section sits inside main after the closing form tag. A missing article closing tag can also merge cards together.

Help me find my missing product card

Your unfiltered shop is ready. Next, you will give each checkbox state a typed home in CSS.

Register Filter State Properties

Your shop page already gives you a visible baseline. Every backpack and duffel card appears in the product grid.

The checkboxes now need a place to store their filter states. A registered custom property gives CSS a typed state value that the browser can validate.

Each property accepts only true or false. Both properties begin at false so the complete product grid remains visible.

In this step, get ready to:
  • Register a typed state property for the Backpack filter.
  • Register a typed state property for the Duffel filter.
  • Confirm both properties resolve to false on the page root.
Start with the backpack state

The Backpack checkbox needs its own state slot. The registration defines its allowed values plus its starting value.

  • Switch back to product-filter.html in the code editor from earlier.
  • Find the existing <style> element.
  • Place this block at the beginning of the existing CSS:
@property --is-backpack {
  /* Limit the filter state to two valid keywords. */
  syntax: "true | false";
  inherits: true;
  initial-value: false;
}

What does this registration do?

  • The @property rule registers --is-backpack so the browser can validate its value.
  • The syntax descriptor limits the property to true or false.
  • The inherits descriptor makes the state available to elements below the page root.
  • The initial-value descriptor gives the property a starting value of false.
  • Save product-filter.html.
  • Return to the browser tab from earlier.
  • Refresh product-filter.html.
  • Use browser DevTools to inspect the :root element.
  • Search the computed values for --is-backpack.

You should see --is-backpack resolve to false. The grid still shows every product card.

Can't find the backpack value?

  • Confirm the @property block sits inside the <style> element.
  • Check the spelling of --is-backpack.
  • Reload the browser page after saving product-filter.html.

Help me find why the registered backpack property is missing.

Add the duffel state

The Duffel checkbox needs an independent state property. This keeps each filter value separate when the checkboxes begin controlling the grid.

  • Return to product-filter.html in your code editor.
  • Place this block directly below the --is-backpack registration:
@property --is-duffel {
  /* Limit the filter state to two valid keywords. */
  syntax: "true | false";
  inherits: true;
  initial-value: false;
}

Why use a second property?

  • The --is-duffel property stores the Duffel filter state independently.
  • The same true | false syntax gives both filters a consistent state format.
  • The initial value keeps the Duffel filter inactive when the page loads.
  • Save product-filter.html.
  • Return to the browser.
  • Refresh the page.
  • Inspect :root again.
  • Search the computed values for --is-duffel.

You should see --is-duffel resolve to false. Every product card should remain visible.

Can't find the duffel value?

  • Confirm the second registration uses --is-duffel.
  • Check that each @property block has its own closing brace.
  • Confirm initial-value is set to false.

Help me debug the duffel property registration.

Confirm both default values

Before you check, what values do you expect the two properties to show? What should the grid look like while both properties use their defaults?

  • Save product-filter.html.
  • Return to the browser tab from earlier.
  • Reload product-filter.html.
  • Use DevTools to inspect :root.
  • Search the computed values for --is-backpack.
  • Search the computed values for --is-duffel.

You should see both properties resolve to false. You should also see every backpack and duffel card in the product grid.

✔️ Awesome, I've got everything!

Your two filter properties are registered. Double-check that you saved product-filter.html.

ⓧ I'd like to double check the full code

The surrounding shop markup stays unchanged from the previous step.

  • Compare the registered-property section in product-filter.html with this reference:
@property --is-backpack {
  /* Limit the filter state to two valid keywords. */
  syntax: "true | false";
  inherits: true;
  initial-value: false;
}

@property --is-duffel {
  /* Limit the filter state to two valid keywords. */
  syntax: "true | false";
  inherits: true;
  initial-value: false;
}

Your CSS now has two validated filter-state properties. Next up, :has() will change each property to true when its checkbox is selected.

Connect Checkboxes to CSS State

Your two registered custom properties now resolve to false on :root. This gives the shop page a dependable default state.

The checkboxes still need a bridge into CSS state. The :has() pseudo-class detects a checked control so its matching rule can change one typed value to true.

Product cards remain visible while you verify this connection in DevTools.

In this step, get ready to:
  • Add two :has() rules that translate checked controls into true values.
  • Verify each control changes its matching custom property.
  • Confirm every product remains visible.
Connect checked controls to state

A checked checkbox has a browser state called :checked. The :has() selector lets :root respond when it contains that state.

Each selector changes only the custom property tied to its control.

  • Return to product-filter.html in your code editor.
  • Find the closing brace of the second @property block.
  • Place the checkbox state rules below that block by pasting this code:
:root:has(#isBackpack:checked) {
  --is-backpack: true;
}

:root:has(#isDuffel:checked) {
  --is-duffel: true;
}

What do these rules do?

  • :root:has(#isBackpack:checked) matches the page root while the Backpack control is checked.
  • The first declaration changes --is-backpack to the registered value true.
  • :root:has(#isDuffel:checked) performs the same job for the Duffel control.
  • The registered initial value resumes automatically when its checkbox is cleared.
  • Save product-filter.html.
  • Switch back to the browser page from earlier.
  • Reload the page.
  • Clear the Backpack checkbox.
  • Clear the Duffel checkbox.
  • Switch back to the DevTools inspector from earlier.
  • Inspect :root in the computed styles view.

You should see both --is-backpack and --is-duffel resolve to false. Every product card should still be visible.

Do the starting values look wrong?

  • Confirm the new selectors sit below both @property blocks in the CSS section.
  • Check that each selector has a complete pair of braces.
  • Save product-filter.html before reloading the browser.

Help me debug the starting custom-property values.

Trace the Backpack value

The computed value shows the final state that CSS resolves for :root.

Before you select Backpack, do you expect both custom properties to change?

  • Switch back to the product page.
  • Select the Backpack checkbox.
  • Switch back to the DevTools inspector.
  • Inspect :root again.

You should see --is-backpack resolve to true. You should see --is-duffel remain false.

  • Switch back to the product page.
  • Clear the Backpack checkbox.
  • Switch back to the DevTools inspector.
  • Inspect :root once more.

You should see --is-backpack return to false.

Does the Backpack value stay false?

  • Check that the checkbox ID is spelled isBackpack.
  • Confirm the selector contains #isBackpack:checked.
  • Reload the browser after saving the file.

Help me debug the Backpack state rule.

Verify the Duffel state

The second selector should respond independently to the Duffel control.

Before you select Duffel, which custom property do you expect to become true?

  • Switch back to the product page.
  • Keep the Backpack checkbox clear.
  • Select the Duffel checkbox.
  • Switch back to the DevTools inspector.
  • Inspect :root.

You should see --is-duffel resolve to true. You should see --is-backpack remain false.

  • Switch back to the product grid.

Every product card should remain visible. This confirms that the checkboxes now control CSS state without filtering the grid.

Does the Duffel value stay false?

  • Check that the checkbox ID is spelled isDuffel.
  • Confirm the selector contains #isDuffel:checked.
  • Check that the declaration sets --is-duffel to true.

Help me debug the Duffel state rule.

That's the state bridge working. Each checkbox now drives its own typed CSS value.

Next, you will turn the page root into a named container so these values can filter the product grid.

Filter Products with Style Queries

Your checkboxes now change the custom-property values on :root. The product grid still shows every card because no rule reads that state.

A container style query can read a custom property from a named container. You will use that state to remove non-matching products from the grid.

In this step, get ready to:
  • Name :root as the product-filters container.
  • Filter the grid when the Backpack checkbox is selected.
  • Filter the grid when the Duffel checkbox is selected.
Name the root container

A named container gives each query an explicit place to read filter state. The name product-filters connects the root element to the queries you add next.

  • Return to product-filter.html in your code editor.
  • Find the rule that starts with :root:has(#isBackpack:checked).
  • Place this new rule immediately above it:
:root {
  /* Give the filter queries a named source for their state */
  container-name: product-filters;
}

What does this code do?

  • The container-name declaration assigns a stable name to :root.
  • The name product-filters lets later queries read the custom properties stored on that element.
  • Save product-filter.html.
  • Refresh the shop page in your browser.
  • Return to :root in DevTools.
  • Find container-name in the computed styles.

That connection is in place: :root now exposes the product-filters container name.

Container name missing?

Check that the new :root rule sits inside the existing style element. Confirm that container-name contains the exact value product-filters.

Help me check why the named container is missing from the computed styles.

Filter for backpacks

The first style query activates when --is-backpack is true. Its selector targets product cards that do not carry the backpack class.

  • Return to the CSS in product-filter.html.
  • Add this query below the existing :root:has() rules:
@container product-filters style(--is-backpack: true) {
  /* Hide products that do not match the active backpack filter */
  .product:not(.backpack) {
    display: none;
  }
}

How does the backpack query work?

  • The query reads --is-backpack from the named product-filters container.
  • The selector .product:not(.backpack) matches product cards outside the backpack category.
  • The declaration display: none removes those non-matching cards from the grid.
  • Save product-filter.html.

Before you test the query, which cards do you expect to remain when only Backpack is checked?

  • Refresh the shop page in your browser.
  • Uncheck the Duffel checkbox.
  • Check the Backpack checkbox.

You will see the non-backpack cards disappear. The grid keeps the product cards marked as backpacks.

Backpack cards not filtering?

Confirm that the query names product-filters. Check that the condition uses --is-backpack: true.

Help me find why the backpack style query is not filtering the product grid.

Filter for duffels

The second query follows the same state-driven pattern. This time it reads --is-duffel to keep the duffel cards visible.

  • Return to the CSS in product-filter.html.
  • Add this query below the backpack query:
@container product-filters style(--is-duffel: true) {
  /* Hide products that do not match the active duffel filter */
  .product:not(.duffel) {
    display: none;
  }
}

How does the duffel query work?

  • The query activates when the root container reports --is-duffel: true.
  • The selector .product:not(.duffel) matches every non-duffel product card.
  • The matching cards receive display: none.
  • Save product-filter.html.

Before you test the second query, which cards should remain when only Duffel is checked?

  • Refresh the shop page in your browser.
  • Uncheck the Backpack checkbox.
  • Check the Duffel checkbox.

You will see the non-duffel cards disappear. The grid keeps the product cards marked as duffels.

Duffel cards not filtering?

Confirm that the condition uses --is-duffel: true. Check that the product selector contains the exact duffel class name.

Help me find why the duffel style query is not filtering the product grid.

That is the filter working: either checkbox now changes which products remain in the grid.

Your product list now responds to CSS state without JavaScript. Next up, you will combine conditions with and to handle every checkbox combination.

Handle Combined Filter States

Your grid can already respond when either filter is checked. The remaining problem appears when the browser must distinguish one checkbox combination from another.

Combined container style queries let CSS evaluate both Boolean values in a single condition.

You will add and conditions to separate the single-filter states. You will then prove the logic across all four checkbox combinations.

In this step, get ready to:
  • Make the Backpack query match only the Backpack-only state.
  • Make the Duffel query match only the Duffel-only state.
  • Capture evidence for all four checkbox combinations.
Make the Backpack query exclusive

An and condition matches when every part of the condition is true. The Backpack rule needs to confirm that Backpack is selected while Duffel is clear.

  • Return to product-filter.html in the code editor from earlier.
  • Find the named @container rule that checks --is-backpack: true.
  • Add and style(--is-duffel: false) immediately after style(--is-backpack: true).
  • Save product-filter.html.
  • Switch back to the browser tab from earlier.
  • Refresh the page.
  • Clear the Duffel checkbox.
  • Select the Backpack checkbox.

You will see only the backpack cards in the grid.

How does the combined condition work?

The query now requires --is-backpack to equal true. It also requires --is-duffel to equal false.

This gives the Backpack-only state its own readable condition.

Still seeing every product?

  • Check that and style(--is-duffel: false) is inside the Backpack query header.
  • Confirm that the query still contains the existing rule that hides non-backpack cards.

Help me debug the Backpack-only container style query.

Make the Duffel query exclusive

The Duffel-only state needs the opposite pair of values. Duffel must be true while Backpack remains false.

  • Return to product-filter.html in your code editor.
  • Find the named @container rule that checks --is-duffel: true.
  • Add and style(--is-backpack: false) immediately after style(--is-duffel: true).
  • Save product-filter.html.
  • Switch back to the browser tab.
  • Refresh the page.
  • Clear the Backpack checkbox.
  • Select the Duffel checkbox.

You will see only the duffel cards in the grid.

Duffel-only state not working?

  • Check that and style(--is-backpack: false) is inside the Duffel query header.
  • Confirm that the query still contains the existing rule that hides non-duffel cards.

Help me debug the Duffel-only container style query.

Test every checkbox combination

Two checkboxes create four possible states. Testing the complete state matrix proves that each combined condition activates at the right time.

Before you test the matrix, what do you expect the grid to show for each checkbox combination?

  • Clear the Backpack checkbox.
  • Clear the Duffel checkbox.

You will see every product card when neither filter is selected.

  • Capture a screenshot of the grid with neither filter selected.
  • Select the Backpack checkbox.

You will see only the backpack cards.

  • Capture a screenshot of the Backpack-only result.
  • Clear the Backpack checkbox.
  • Select the Duffel checkbox.

You will see only the duffel cards.

  • Capture a screenshot of the Duffel-only result.
  • Select the Backpack checkbox.

You will see the backpack cards together with the duffel cards.

  • Capture a screenshot of the result with both filters selected.

You have proved that the grid handles every combination of its two filter values. Next, you will test how the registered properties respond to an invalid value.

Test Invalid Property Values

Your four screenshots prove that the filter behaves correctly for every valid checkbox state. This final test probes what happens when the filter receives an invalid state.

A registered custom property checks values against its declared syntax. Browser DevTools lets you test that boundary without changing your saved file.

In this step, get ready to:
  • Apply the temporary value maybe through DevTools.
  • Observe how the registered property handles the invalid value.
  • Confirm that the saved filter still uses valid true-or-false states.
Test an invalid value

The @property registration limits --is-backpack to the grammar true | false. The value maybe deliberately falls outside that grammar.

This part can feel fiddly because developer tools arrange editable declarations differently across browsers. The target is the existing :root rule.

  • Switch back to the browser tab displaying product-filter.html from earlier.
  • Clear the Backpack checkbox.
  • Clear the Duffel checkbox.
  • Return to the DevTools panel from earlier.
  • Select the page's :root element.

Before you test the value, predict whether maybe survives as the computed state or the property returns to its initial value.

  • Add a temporary --is-backpack declaration with the value maybe to the editable :root rule.
  • Read the computed value of --is-backpack.

You will see false as the computed value. The invalid declaration cannot replace the registered initial value.

You have now proved that the filter state stays inside its two registered values.

Why Does the Value Resolve to False?

The @property registration validates --is-backpack against true | false when the browser computes the property.

The value maybe fails that grammar. The computed property uses initial-value: false.

Still Seeing True?

  • Clear the Backpack checkbox before reading the property.
  • Confirm that the temporary declaration targets --is-backpack.
  • Confirm that the @property registration still declares true | false as its syntax.

Help me diagnose the invalid property test.

Remove the temporary test

Changes made through DevTools last only for the current page session. Reloading the page restores the state defined in product-filter.html.

  • Remove the temporary --is-backpack declaration from DevTools.
  • Reload the browser page.

You will see every product in the grid because both checkboxes are clear. The computed value of --is-backpack returns to false.

  • Switch back to product-filter.html in the editor from earlier.
  • Search the saved file for maybe.

Your search should find no matches. The saved file still contains only valid true and false property values.

Found Maybe in the Saved File?

  • Remove the temporary --is-backpack declaration if you added it as a new line.
  • Restore initial-value: false if you changed the property registration.
  • Save product-filter.html.

Help me restore the saved filter.

Recheck the saved filter

The final check confirms that the temporary test is gone. It also proves that the checkbox still controls the registered property.

  • Return to the browser tab displaying product-filter.html.
  • Reload the saved page.

You will see the full product grid. No temporary maybe declaration remains in the page.

Before the final check, predict how the property value and product grid respond when you select Backpack.

  • Select the Backpack checkbox.
  • Read the computed value of --is-backpack on :root.

You will see --is-backpack computed as true. The grid shows the matching backpack products.

  • Clear the Backpack checkbox.
  • Read the computed value of --is-backpack again.

You will see false again. Every product returns to the grid.

That is the final guardrail confirmed. Your saved filter rejects invalid state while its checkbox flow keeps working.

Secret mission

Audit Keyboard-Only Filtering

Audit your finished product filter without touching the mouse. Use the keyboard to reach both checkboxes. Test all four states. Capture proof that the grid follows the controls.

Clean Up Your Resources

Clean Up Your Resources

Your finished filter lives entirely in local files with no ongoing costs or cloud resources. Decide whether to keep the files for reference, pause by closing your tools, or delete everything.

Resources you used:

  • The product-filter.html file containing the complete product filter.
  • Four local screenshots showing every checkbox combination.

Keep everything running

No action needed. Choose this if you want to reuse the filter pattern or compare browser support later.

  • Keep product-filter.html in its current location as a working reference.
  • Keep the four screenshots as evidence of every filter state.
  • Reopen product-filter.html in your supported browser whenever you want to test the filter again.

Pause - I'll come back to this later

No server or background process needs to be stopped. Pausing simply closes the tools while your local files remain available.

  • Close the browser tab showing product-filter.html.
  • Close your code editor.
  • Leave product-filter.html and the four screenshots in their current locations.

Delete - I don't want to use this again

Removing these files erases the working example plus its four test records. Your deleted-items folder may also contain unrelated files that you still need.

macOS

On macOS, Finder manages the page file plus its screenshots.

  • Open Finder from the macOS application launcher.
  • Return to the folder containing product-filter.html.
  • Delete product-filter.html from the file's context menu.
  • Return to the folder where you saved the four checkbox-combination screenshots.
  • Select the four screenshot files.
  • Delete the selected screenshots from the selection's context menu.
Finish the cleanup
  • Open the deleted-items area from the Finder sidebar.
  • Restore any unrelated files that you want to keep.
  • Empty the deleted-items area.
  • Confirm that product-filter.html and the four screenshots no longer appear in their original folders.

Windows

On Windows, File Explorer manages the page file plus its screenshots.

  • Open File Explorer from the Windows application launcher.
  • Return to the folder containing product-filter.html.
  • Delete product-filter.html from the file's context menu.
  • Return to the folder where you saved the four checkbox-combination screenshots.
  • Select the four screenshot files.
  • Delete the selected screenshots from the selection's context menu.
Finish the cleanup
  • Open the deleted-items area from the File Explorer sidebar.
  • Restore any unrelated files that you want to keep.
  • Empty the deleted-items area.
  • Confirm that product-filter.html and the four screenshots no longer appear in their original folders.

Nice Work!

Nice Work!

Fantastic work! Your one-file shop now filters products with HTML and CSS without any JavaScript.

You've learned how to:

  • Register typed custom properties with @property so each filter accepts only true or false.
  • Turn checkbox state into CSS state with :has() while confirming the changing values in browser DevTools.
  • Combine container style queries with and to filter the product grid across all four checkbox combinations.
  • Secret Mission: Took on an optional challenge to push your CSS filtering skills further.

Ready to quiz yourself?