Skip to content

fix(tags): make tags and their picker accessible - #8613

Closed
talissoncosta wants to merge 73 commits into
mainfrom
fix/tags-a11y-8465
Closed

talissoncosta wants to merge 73 commits into
mainfrom
fix/tags-a11y-8465

Conversation

@talissoncosta

@talissoncosta talissoncosta commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor
  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Closes #8465. Replaces #8569 and #8614.

Tags drew the stored colour as text on an 8% tint of itself, so most of the palette failed AA. The
picker was worse: swatches with no accessible name, a selected state only a sighted user could see,
and a checkbox drawn inside the chip it was selecting.

Colour. Custom tags take one of eleven Content tints, and every colour a tag can hold maps onto
one, so nothing migrates. System tags keep the plain surface and carry their state in an icon. Same
fill on both themes, per Dragos's frame.

Picker. A row owns the selection, with role="checkbox", aria-checked and arrow keys; per-tag
actions move into a menu. ToggleChip becomes a selected prop on Chip and goes. The swatch grid
is a ColorSwatch in a button, named by its hue. A plan-locked tag is focusable now, so arrow keys
pass it and its tooltip can be read.

Creating a tag. The "Add New Tag" button becomes one row under the list: named it creates the
tag, unnamed it opens the full form.

Pre-existing bugs fixed on the way:

  • Both tag searches were case-sensitive, and a name differing only in case could be created twice.
  • Saving an edit never closed the form: the effect ran on editSuccess but asked createSuccess.
  • The permanent tag's lock has been dead since fix: Handle invalid colour codes on tags, allow default colours #4822 dropped an argument from renderIcon.
  • DropdownMenu portals to the body, so picking Edit opened the tag form and shut it again.

Known and deliberate:

  • The fill is 1.18:1 against white, under the 3:1 for non-text. A tag is read from its label, which
    clears AA on every tint, not from its edge.
  • TableTagFilter's dot still draws the stored colour, so an older tag shows a saturated dot beside
    a pale chip. A 16px circle in a chip tint is close to invisible, so it needs a design answer.
  • A colour we never issued falls back to the neutral surface, which on a light page reads as plain
    text rather than as a chip.

Not here: the Chip variants (#8612) and the ~60 legacy .chip sites.

How did you test this code?

tagSwatches.test.ts asserts every label clears 4.5:1 and that no two tints collide;
tagSwatch.test.ts pins every colour a tag can hold to its swatch. Unit suite and E2E pass, tsc
unchanged against main. Storybook in both themes, under Components → Tags.

To check manually, in a project with several coloured tags:

  1. Feature list, tag filter and colour picker, light and dark.
  2. The tag panel: hover a row for the menu, arrow through it, pick Edit. The form should open, stay
    open, and close on save.
  3. Type an existing tag's name into the search box and press Enter. Nothing should be created.
  4. Edit a tag made before this branch. Its swatch should be ringed.
  5. A system tag (a linked GitHub PR) for the icon and no fill; a permanent tag for the lock.

@talissoncosta
talissoncosta requested a review from a team as a code owner September 29, 2026 13:18
@talissoncosta
talissoncosta requested review from kyle-ssg and removed request for a team September 29, 2026 13:18
@vercel

vercel Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
flagsmith-frontend-preview Ready Ready Preview Sep 30, 2026 12:53pm UTC
flagsmith-frontend-staging Ready Ready Preview Sep 30, 2026 12:53pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
docs Ignored Ignored Preview Sep 30, 2026 12:53pm UTC

Request Review

@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

The frontend adds a content-colour palette with generated TypeScript mappings, CSS variables, tag utilities, and contrast checks. Tag rendering now uses shared chips, swatch utilities, and system-tag icons. A shared picker is used for tag colour selection. Tag management and filtering update row interactions, creation checks, and search behaviour. Storybook coverage and end-to-end selectors are updated.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Severity of issue fixed: Medium

Merge Risk: 🟡 Moderate · up to 2a3d1

Three tag-picker problems should be fixed before merge. A feature with no tags selected may not allow tags to be added. Saving an edit to a tag can silently add it to or remove it from the feature. Keyboard users cannot reach Edit or Delete from the new action menu. A smaller issue is that some unusual stored colour values can break tag rendering.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added front-end Issue related to the React Front End Dashboard fix labels Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Docker builds report

Image Build Status Security report
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8613 Finished ✅ Results ✅
ghcr.io/flagsmith/flagsmith-e2e:pr-8613 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith-api:pr-8613 Finished ✅ Results ✅
ghcr.io/flagsmith/flagsmith-api-test:pr-8613 Finished ✅ Skipped
ghcr.io/flagsmith/flagsmith:pr-8613 Finished ✅ Results ✅
ghcr.io/flagsmith/flagsmith-frontend:pr-8613 Finished ✅ Results ✅
ghcr.io/flagsmith/flagsmith-frontend:pr-8613 Finished ✅ Results ✅
ghcr.io/flagsmith/flagsmith-private-cloud:pr-8613 Finished ✅ Results ✅

@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor
✅ private-cloud · depot-ubuntu-latest-arm-16 — run #21044 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  41.5 seconds
commit  cb455c8
info  🔄 Run: #21044 (attempt 1)

🗂️ Previous results
✅ private-cloud · depot-ubuntu-latest-16 — run #21044 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  4 passed

Details

stats  4 tests across 4 suites
duration  32.2 seconds
commit  cb455c8
info  🔄 Run: #21044 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #21044 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  37.7 seconds
commit  cb455c8
info  🔄 Run: #21044 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #21042 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  38.6 seconds
commit  c8e8af4
info  🔄 Run: #21042 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #21044 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  31.7 seconds
commit  cb455c8
info  🔄 Run: #21044 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #21042 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  57.8 seconds
commit  c8e8af4
info  🔄 Run: #21042 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #21043 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  35.6 seconds
commit  dfc59a7
info  🔄 Run: #21043 (attempt 1)

✅ oss · depot-ubuntu-latest-arm-16 — run #21042 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-arm-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  35 seconds
commit  c8e8af4
info  🔄 Run: #21042 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #21042 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  31 seconds
commit  c8e8af4
info  🔄 Run: #21042 (attempt 1)

✅ oss · depot-ubuntu-latest-16 — run #21043 (attempt 1)

Playwright Test Results (oss - depot-ubuntu-latest-16)

passed  1 passed

Details

stats  1 test across 1 suite
duration  31.2 seconds
commit  dfc59a7
info  🔄 Run: #21043 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-arm-16 — run #21033 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-arm-16)

passed  3 passed

Details

stats  3 tests across 3 suites
duration  1 minute, 11 seconds
commit  a6df67d
info  🔄 Run: #21033 (attempt 1)

✅ private-cloud · depot-ubuntu-latest-16 — run #21033 (attempt 1)

Playwright Test Results (private-cloud - depot-ubuntu-latest-16)

passed  2 passed

Details

stats  2 tests across 2 suites
duration  32 seconds
commit  a6df67d
info  🔄 Run: #21033 (attempt 1)

talissoncosta and others added 6 commits September 29, 2026 10:50
Eleven tints from the design system's Content palette, plus the ink
their labels take. A tag is the same on both themes, fill and dark
label, so there is no dark counterpart and the utility reads the
primitives directly.

The fill sits at 1.18:1 against white, under the 3:1 for non-text. That
is the design as handed off and it holds: a tag is read from its label,
which clears AA on every tint, not from its edge. The test pins that,
and that no two tints collide, since tags are told apart by colour
alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The API already creates a dequeued-PR tag; the UI rendered it without an
icon.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tags colour a chip from a class, because the colour is a decorative one
a user picked rather than a semantic role. A variant that emits no
utilities keeps that class from having to beat the defaults on source
order alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tags rendered the stored colour as text on an 8% tint of itself, so
contrast was whatever the hue gave and most of the palette failed AA.
The maths was copied to four places, two of them inline style strings
behind dangerouslySetInnerHTML.

A tag now takes the nearest Content tint, matched on OKLCH hue, so no
tag changes identity and nothing migrates. `color` is an unvalidated
CharField, so matching rather than a lookup table is what makes every
tag land on an accessible pair.

System tags carry their state in a coloured icon on plain surface, so it
survives for anyone who cannot tell fills apart.

Two fixes fall out. `Tag` took `disabled` from the plan the account is
on, which is the caller's to know and pulled the whole utils module into
anything drawing a tag. And the lock on a permanent tag comes back: it
has been dead since #4822 dropped the last argument from the renderIcon
call, leaving `isPermanent` undefined.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The picker drew its options as tags with a label, so you chose a colour
by reading the word on it. Each option is now the colour itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta
talissoncosta force-pushed the feat/chip-variants-statusbadge branch from 00fd156 to 0eab747 Compare September 29, 2026 13:53
@talissoncosta
talissoncosta changed the base branch from feat/chip-variants-statusbadge to main September 29, 2026 13:53
@github-actions github-actions Bot added fix and removed fix labels Sep 29, 2026
talissoncosta and others added 13 commits September 29, 2026 22:51
sort-keys-fix had put 2xl first and xl last, so the scale read in no
order at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It promised "a menu, a usage count" while the CSS animates to a fixed
20px, so anything wider than the menu button is clipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The edit effect ran on editSuccess but asked createSuccess, so saving an
edit never called onComplete and the form stayed open on a tag that had
already saved.

save() also carried its own copy of the disabled condition, without the
duplicate-name check the button has, so Enter created the tag the form
was refusing. One condition now serves both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It lowercased the label and then compared the raw search term, so ONB
found no Onboarding. Same bug as the tag panel's, in the filter the
features list, the feature page and Import all use.

Drops a filter((tag) => tag) that allocated a new array per render.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"archived" and "untagged" ignored the search box, so a term matching
nothing showed "No tags" with two rows underneath it. They filter on
their own labels now, and the message waits until nothing is left.

Names the untagged id, which was written out as '' four times.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A 16px circle of one colour is what ColorSwatch is, and it is decorative
in both, so aria-hidden comes with it. Tag.scss held nothing else.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The disabled note carried its own decision record, and the Partial note
sat on onClick while explaining tag, half of it describing a create row
that no longer previews one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Giving the base a fixed height left --xs inheriting 32px, nearly double
what its padding used to produce, across nine components outside tags.
Stated now, as --sm already was.

onKeyDown replaced the built-in activation instead of running beside it,
so a chip with a caller's handler could not be activated by Enter or
Space. SdkPicker is the one that has one.

onClick and onRemove are now mutually exclusive in the type: a button
inside a button reaches a screen reader as neither.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Against main the only change to --xs is now height: auto, so it renders
exactly as it did rather than at a number I worked back from the padding.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Tag is a display chip in a feature row and a control in the filter, so
placing it by role forces a choice the component does not make. Same
shape as Components/Charts and Components/Diff.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
createTag waited on .tag--select, a class that went with the old picker,
and on an "Add New Tag" button that is a row now. The first timed out at
20s and failed the run on every retry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (3)

🟠 Major · Pass false when no tags are selected. · AddEditTags.tsx:193

frontend/web/components/tags/AddEditTags/AddEditTags.tsx:193
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass false when no tags are selected.

value is optional. When value is undefined, this expression passes checked={undefined}. The new TagRow then sets isCheckbox to false and disables its selection button. Users cannot select an existing tag from this initial state.

Proposed fix
-                    checked={value?.includes(tag.id)}
+                    checked={value?.includes(tag.id) ?? false}
🟠 Major · Preserve tag selection after editing. · AddEditTags.tsx:278

frontend/web/components/tags/AddEditTags/AddEditTags.tsx:278
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Preserve tag selection after editing.

The corrected edit-success effect in frontend/web/components/tags/CreateEditTag.tsx, Line 75, now invokes this callback. selectTag toggles membership. Saving an already selected tag therefore removes it from the feature. Saving an unselected tag adds it.

Return to the selection view without calling selectTag after an edit. Keep automatic selection for newly created tags.

Proposed fix
-          onComplete={(tag: TTag) => {
-            selectTag(tag)
+          onComplete={() => {
             setTab('SELECT')
           }}
🟠 Major · Make the tag actions keyboard-operable. · DropdownMenu.tsx:80

frontend/web/components/base/DropdownMenu.tsx:80
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Make the tag actions keyboard-operable.

The new tag rows use this menu for Edit and Delete. Its action elements are div elements with only onClick. They have no tabIndex or keyboard handler. Keyboard users can open the menu but cannot focus or activate either action.

Use focusable action buttons. Move focus into the menu when it opens, and support closing it with Escape and restoring trigger focus. If this component uses menu semantics, implement the corresponding menu roles and keyboard behaviour. (w3.org)

Based on learnings: non-native clickable controls require keyboard activation equivalent to their click handler.

Source: Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 0c506c81-2499-4dec-84db-7408fc5db3c4

📥 Commits

Reviewing files that changed from the base of the PR and between 0a5a050 and 2a3d1a6.

📒 Files selected for processing (36)
  • frontend/common/theme/tokens.ts
  • frontend/common/useContainClicks.ts
  • frontend/documentation/components/Chip.stories.tsx
  • frontend/documentation/components/ColorSwatch.stories.tsx
  • frontend/documentation/components/Icons.stories.tsx
  • frontend/documentation/components/Tag.stories.tsx
  • frontend/documentation/components/TagColourPicker.stories.tsx
  • frontend/documentation/components/VCSProviderTag.stories.tsx
  • frontend/e2e/helpers/e2e-helpers.playwright.ts
  • frontend/scripts/generate-tokens.mjs
  • frontend/web/components/ColorSwatch.tsx
  • frontend/web/components/base/Chip/Chip.scss
  • frontend/web/components/base/Chip/Chip.tsx
  • frontend/web/components/base/DropdownMenu.tsx
  • frontend/web/components/feature-summary/FeatureAction.tsx
  • frontend/web/components/icons/Icon.tsx
  • frontend/web/components/pages/environment-settings/EnvironmentSettingsPage.tsx
  • frontend/web/components/tables/TableTagFilter.tsx
  • frontend/web/components/tags/AddEditTags/AddEditTags.scss
  • frontend/web/components/tags/AddEditTags/AddEditTags.tsx
  • frontend/web/components/tags/ColourSelect/ColourSelect.scss
  • frontend/web/components/tags/ColourSelect/ColourSelect.tsx
  • frontend/web/components/tags/ColourSelect/index.ts
  • frontend/web/components/tags/CreateEditTag.tsx
  • frontend/web/components/tags/Tag/Tag.tsx
  • frontend/web/components/tags/TagColourPicker/TagColourPicker.scss
  • frontend/web/components/tags/TagColourPicker/TagColourPicker.tsx
  • frontend/web/components/tags/TagContent.tsx
  • frontend/web/components/tags/TagFilter.tsx
  • frontend/web/components/tags/TagRow/TagRow.scss
  • frontend/web/components/tags/TagRow/TagRow.tsx
  • frontend/web/components/tags/utils/__tests__/tagSwatch.test.ts
  • frontend/web/components/tags/utils/index.ts
  • frontend/web/components/tags/utils/systemTag.ts
  • frontend/web/components/tags/utils/tagChipHtml.ts
  • frontend/web/components/tags/utils/tagSwatch.ts

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread frontend/web/components/tags/utils/tagSwatch.ts Outdated
talissoncosta and others added 2 commits September 30, 2026 07:53
A tag whose stored colour is "constructor" or "__proto__" found an
inherited property, so getTagSwatch handed back something that is not a
swatch: a garbage utility class, and a throw in swatchLabel.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
as const told the compiler what the pair is; a return type has it check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/tags/TagFilter.tsx
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

Captured checks passed, including unit tests and linting. The compact tag filter now has a selectable Stale tag path without the plan-entitlement guard, so this accessibility refactor should not merge until that regression is fixed.

Area Score
🎯 Correctness 3/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

  • frontend/web/components/tags/TagFilter.tsx:104 — locked Stale tags can be selected from the compact filter.

⚖️ Acknowledged

  • Selectable system tags receiving a custom swatch — thread resolved by @talissoncosta
  • Palette story measuring the wrong tag ink/fills — thread resolved by @coderabbitai[bot]
  • VCS story examples lacking recognised labels — thread resolved by @coderabbitai[bot]
  • Story description for an outside-scale colour — thread resolved by @coderabbitai[bot]
  • Static colour-picker stories missing onChange — thread resolved by @coderabbitai[bot]
  • ToggleChip story using a missing utility — thread resolved by @coderabbitai[bot]
  • Clickable system tags using the wrong surface — thread resolved by @coderabbitai[bot]
  • Disabled tags remaining on the active button path — thread resolved by @coderabbitai[bot]
  • Colour swatches lacking accessible names — thread resolved by @coderabbitai[bot]
  • Toggle state not exposed to assistive technology — thread resolved by @coderabbitai[bot]
  • aria-checked applied to button-role chips — thread resolved by @coderabbitai[bot]
  • Disabled selectable rows missing their accessibility state — thread resolved by @coderabbitai[bot]
  • Arrow keys firing from nested row controls — thread resolved by @coderabbitai[bot]
  • Nested menu controls inside checkbox rows — thread resolved by @coderabbitai[bot]
  • Existing tag colours not selecting their mapped tint — thread resolved by @coderabbitai[bot]
  • Arbitrary stored colours losing their documented rendering — thread resolved by @coderabbitai[bot]
  • aria-pressed falling back onto non-buttons — thread resolved by @coderabbitai[bot]
  • Disabled-row arrow navigation — thread resolved by @talissoncosta
  • Inherited properties leaking into colour lookups — thread resolved by @coderabbitai[bot]
📝 Walkthrough
  • Tag rendering — maps stored and legacy tag colours to the Content palette while system tags use icon-led neutral chips.
  • Tag management — replaces chip-contained selection with accessible rows, actions menus, and a colour swatch picker.
  • Filters — makes tag search case-insensitive and allows the table filter to search archived and untagged pseudo-rows.
  • Shared controls — updates Chip selection semantics and contains clicks from portalled dropdown menus.
🧪 How to verify
  1. In an organisation without STALE_FLAGS, open a feature import/export tag filter and confirm a Stale tag cannot be selected.
  2. In the tag panel, navigate rows with Tab and arrow keys, then open Edit from the row menu and save the change.
  3. Create and edit tags using both current and legacy stored colours; confirm the selected swatch and rendered chip agree in light and dark themes.
  4. Run cd frontend && npm run test:unit -- common/theme/__tests__/tagSwatches.test.ts web/components/tags/utils/__tests__/tagSwatch.test.ts.
    Automate: add a TagFilter test covering a locked Stale tag.

Product take: This is a meaningful accessibility improvement for tag discovery and editing. The entitlement regression is narrow but lets a non-entitled user activate a plan-gated filter, so it needs correction before release.

🧭 Assumptions & unverified claims

Focused local tests could not be rerun because frontend dependencies are unavailable in this checkout.

A much friendlier tag picker, once the Stale gate is put back on its hinge · reviewed at a6df67d

Comment thread api/seed_cr_override.py Fixed
Tag used to read Utils.tagDisabled itself; moving that to a prop left
this filter passing none, so an organisation without STALE_FLAGS could
select the Stale tag here. The other clickable call sites render
pseudo-tags with no type, which tagDisabled never locks.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@talissoncosta

Copy link
Copy Markdown
Contributor Author

@themis-blindfold review

Comment thread frontend/web/components/tags/AddEditTags/AddEditTags.tsx
@themis-blindfold

Copy link
Copy Markdown
Contributor

⚖️ Themis review: 🟠 Fix before merge

CI has passed, and the tag colour and selection changes are coherent. One accessibility gap remains: the new per-tag Edit/Delete actions are mouse-only, so keyboard users cannot manage custom tags.

Area Score
🎯 Correctness 3/5
🧪 Test coverage 3/5
📐 Code quality 4/5
🚀 Product impact 3/5

🟠 Majors

⚖️ Acknowledged

  • Selectable system tags receiving a custom swatch — thread resolved by @talissoncosta
📝 Walkthrough
  • Tag rendering - maps legacy and current colours to Content-palette utilities while keeping system tags on the default surface.
  • Picker - replaces empty chips with named, pressed-state swatch buttons and recognises legacy stored colours.
  • Tag management - uses checkbox rows, plan-lock handling, case-insensitive search, and an inline create row.
  • Shared UI - extracts portal click containment and reuses escaped chip markup in tooltips.
🧪 How to verify
  1. In the tag panel, use Tab and Enter to open a custom tag's actions, then reach and activate Edit and Delete without a mouse.
  2. Arrow through selectable rows, including a plan-locked Stale tag, and confirm its tooltip remains available while it cannot be selected.
  3. Edit a legacy-coloured tag and confirm its matching picker swatch is selected; create a new tag and verify the selection persists.
  4. Search for an existing tag with different casing and press Enter; confirm no duplicate is created.
  5. Open Edit from the tag panel and save; confirm the form returns to the picker without being dismissed by the portalled menu.
    Automate: Add keyboard interaction coverage for the tag-row actions and selection flow.

Product take: This is a solid accessibility improvement for tag selection and colour choice.
The inaccessible action menu still blocks keyboard users from completing tag-management tasks, so it matters before release.

🧭 Assumptions & unverified claims

The checked-out origin/main ref has no merge base with HEAD; this review compared the available 50-commit PR series from its initial commit.

The swatches can speak now; the action menu still needs a keyboard · reviewed at cb455c8

@talissoncosta

Copy link
Copy Markdown
Contributor Author

Closing in favour of a stack, after feedback that this was too big to review. 73 commits and 57 files became five PRs, each reviewable on its own:

#8685 fix(menu): keep a click on a menu item inside its own panel +34, 2 files
#8686 feat(tokens): add the content tag palette +243/-40, 9 files
#8687 feat(icons): add stale, pr-dequeued and lock-outline +80/-1, 2 files
#8688 feat(chip): let a caller supply the colour, and ring it when selected +88/-36, 4 files
#8689 refactor(tags): build tag on chip and look its colour up +566/-497, 25 files

#8689 carries the accessibility fix this was opened for: the colour signal off the fill, onto a neutral border with a coloured icon.

Parked, not dropped. The colour picker and the tag panel are not in the stack. Their container is one of the eleven InlineModal sites due to be rebuilt on the floating layer, so reworking them now means doing it twice. The work is written and will go up once that lands. Everything in this PR's "Known and deliberate" section still applies to it.

Two things worth carrying over:

  • This branch's common/types/responses.ts predates User.is_organisation_membership_active (feat(SCIM): Display deactivated org memberships #8649) and would have reverted it. The stack keeps main's version and takes only the TagType change.
  • The projectId: string vs number errors in the tag files are pre-existing on main, not introduced here.

Please leave fix/tags-a11y-8465 on the remote: it is where the parked picker and panel work lives.

#8465 stays open until the parked half lands.

This branch was successfully deployed

2 active deployments
Preview – flagsmith-frontend-preview — cb455c86 Deployed Sep 30, 2026 by vercel[bot]
Preview – flagsmith-frontend-staging — cb455c86 Deployed Sep 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API fix front-end Issue related to the React Front End Dashboard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tags optimisation

4 participants