diff --git a/_includes/appendix-filter.html b/_includes/appendix-filter.html
new file mode 100644
index 000000000..5f9755a4e
--- /dev/null
+++ b/_includes/appendix-filter.html
@@ -0,0 +1,25 @@
+
+
Filter by rules — show only licenses that match every selected attribute:
+
+ {% assign types = "permissions|conditions|limitations" | split: "|" %}
+ {% assign seen_tags = '' %}
+ {% for type in types %}
+
+ {{ type | capitalize }}
+ {% assign rules = site.data.rules[type] | sort: "label" %}
+ {% for rule_obj in rules %}
+ {% if seen_tags contains rule_obj.tag or rule_obj.tag contains '--' %}
+ {% continue %}
+ {% endif %}
+ {% capture seen_tags %}{{ seen_tags | append:rule_obj.tag }}{% endcapture %}
+
+
+ {{ rule_obj.label }}
+
+ {% endfor %}
+
+ {% endfor %}
+
+
Clear filters
+
+
diff --git a/_includes/css/responsive.css b/_includes/css/responsive.css
index 64524f36b..a79da24b7 100644
--- a/_includes/css/responsive.css
+++ b/_includes/css/responsive.css
@@ -164,6 +164,51 @@
}
}
+.appendix-table-wrapper {
+ overflow-x: auto;
+ max-width: 100%;
+ margin-bottom: 2rem;
+}
+
+.appendix-filter {
+ margin: 1.5rem 0;
+ padding: 1rem;
+ border: 1px solid #ddd;
+ border-radius: 6px;
+ background: color-mix(in srgb, var(--backgroundColor) 92%, #149ad4 8%);
+}
+
+.appendix-filter-groups {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+}
+
+.appendix-filter-group {
+ border: none;
+ margin: 0;
+ padding: 0;
+ min-width: 12rem;
+}
+
+.appendix-filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ margin: 0.15rem 0.5rem 0.15rem 0;
+ font-size: 0.85rem;
+ font-weight: normal;
+}
+
+.appendix-filter-clear {
+ margin-top: 0.75rem;
+}
+
+.appendix-filter-status {
+ margin: 0.5rem 0 0;
+ font-size: 0.85rem;
+}
+
/* Smartphones (portrait) ----------- */
@media only screen and (max-width : 321px) {
.container {
diff --git a/appendix.md b/appendix.md
index 62c119d98..cf421dd33 100644
--- a/appendix.md
+++ b/appendix.md
@@ -9,8 +9,12 @@ For reference, here is a table of every license described in the [choosealicense
If you're here to choose a license, **[start from the home page](/)** to see a few licenses that will work for most cases.
-
+{% include appendix-filter.html %}
+
+
+
{% assign types = "permissions|conditions|limitations" | split: "|" %}
+
License
{% assign seen_tags = '' %}
@@ -25,9 +29,12 @@ If you're here to choose a license, **[start from the home page](/)** to see a f
{% endfor %}
{% endfor %}
+
+
{% assign licenses = site.licenses | sort: "path" %}
{% for license in licenses %}
- {{ license.title }}
+ {% capture rule_tags %}{% for t in types %}{% for r in license[t] %}{{ r }},{% endfor %}{% endfor %}{% endcapture %}
+ {{ license.title }}
{% assign seen_tags = '' %}
{% for type in types %}
{% assign rules = site.data.rules[type] | sort: "label" %}
@@ -62,7 +69,9 @@ If you're here to choose a license, **[start from the home page](/)** to see a f
{% endfor %}
{% endfor %}
+
+
## Legend
@@ -93,3 +102,5 @@ If you're here to choose a license, **[start from the home page](/)** to see a f
{% endfor %}
{% endfor %}
+
+
diff --git a/assets/js/appendix-filter.js b/assets/js/appendix-filter.js
new file mode 100644
index 000000000..9de502520
--- /dev/null
+++ b/assets/js/appendix-filter.js
@@ -0,0 +1,52 @@
+class AppendixFilter {
+ constructor(root) {
+ this.root = root;
+ this.table = document.querySelector('.appendix-table');
+ this.rows = Array.from(this.table?.querySelectorAll('tbody tr') || []);
+ this.checkboxes = Array.from(root.querySelectorAll('input[type="checkbox"]'));
+ this.statusEl = document.querySelector('#appendix-filter-status');
+ this.clearBtn = document.querySelector('#appendix-filter-clear');
+
+ this.checkboxes.forEach((cb) => cb.addEventListener('change', () => this.apply()));
+ this.clearBtn?.addEventListener('click', () => {
+ this.checkboxes.forEach((cb) => { cb.checked = false; });
+ this.apply();
+ });
+
+ this.apply();
+ }
+
+ selectedRules() {
+ return this.checkboxes.filter((cb) => cb.checked).map((cb) => cb.value);
+ }
+
+ rowMatches(row, rules) {
+ if (!rules.length) return true;
+ const rowTags = (row.dataset.rules || '').split(',').filter(Boolean);
+ return rules.every((rule) => rowTags.includes(rule));
+ }
+
+ apply() {
+ const rules = this.selectedRules();
+ let visible = 0;
+
+ this.rows.forEach((row) => {
+ const match = this.rowMatches(row, rules);
+ row.hidden = !match;
+ if (match) visible += 1;
+ });
+
+ if (this.statusEl) {
+ if (!rules.length) {
+ this.statusEl.textContent = `Showing all ${this.rows.length} licenses.`;
+ } else {
+ this.statusEl.textContent = `Showing ${visible} of ${this.rows.length} licenses matching ${rules.length} selected rule(s).`;
+ }
+ }
+ }
+}
+
+document.addEventListener('DOMContentLoaded', () => {
+ const root = document.querySelector('#appendix-filter');
+ if (root) new AppendixFilter(root);
+});