Skip to content

fix(core/blind): add visible focus outline to blind header#2597

Open
SaiYugandhar03 wants to merge 10 commits into
siemens:mainfrom
SaiYugandhar03:Keyboard-Accessibility-For-Ix-Blind
Open

fix(core/blind): add visible focus outline to blind header#2597
SaiYugandhar03 wants to merge 10 commits into
siemens:mainfrom
SaiYugandhar03:Keyboard-Accessibility-For-Ix-Blind

Conversation

@SaiYugandhar03

@SaiYugandhar03 SaiYugandhar03 commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

💡 What is the current behavior?

The blind header could be toggled but did not show a clear keyboard focus state around the header wrapper

Jira ticket: 4338

🆕 What is the new behavior?

The blind header now displays a visible focus outline around the wrapper

🏁 Checklist

A pull request can only be merged if all of these conditions are met (where applicable):

  • 🦮 Accessibility (a11y) features were implemented
  • 🗺️ Internationalization (i18n) - no hard coded strings
  • 📲 Responsiveness - components handle viewport changes and content overflow gracefully
  • 📕 Add or update a Storybook story
  • 📄 Documentation was reviewed/updated siemens/ix-docs
  • 🧪 Unit tests were added/updated and pass (pnpm test)
  • 📸 Visual regression tests were added/updated and pass (Guide)
  • 🧐 Static code analysis passes (pnpm lint)
  • 🏗️ Successful compilation (pnpm build, changes pushed)

👨‍💻 Help & support

Summary by CodeRabbit

Summary

  • Style
    • Refined ix-blind header label/sublabel and action icon colors across variants for more consistent contrast and theme alignment.
  • Accessibility
    • Added a visible focus outline for the blind header when keyboard-focused.
    • Updated the collapse chevron icon to be hidden from screen readers (aria-hidden="true").
  • Tests
    • Added an Axe-based accessibility regression test for ix-blind to ensure no violations are introduced.
  • Release Notes
    • Updated the changeset to reflect the blind accessibility improvements.

@changeset-bot

changeset-bot Bot commented Jun 10, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6974499

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 5 packages
Name Type
@siemens/ix Patch
@siemens/ix-angular Patch
@siemens/ix-docs Patch
@siemens/ix-react Patch
@siemens/ix-vue Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@netlify

netlify Bot commented Jun 10, 2026

Copy link
Copy Markdown

Deploy Preview for ix-storybook ready!

Name Link
🔨 Latest commit 6974499
🔍 Latest deploy log https://app.netlify.com/projects/ix-storybook/deploys/6a3bb54c79db400008ce22a9
😎 Deploy Preview https://deploy-preview-2597--ix-storybook.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request refactors the layout and DOM structure of the blind component's header. Feedback highlights a critical accessibility issue where nesting the custom-header and header-actions slots inside the <button> element creates invalid nested interactive controls; moving these slots outside the button is recommended, along with adjusting the .blind-header width to auto. Additionally, the removal of --ix-icon-button-color causes a loss of contrast for icon buttons in colored variants, which should be restored. Finally, an axe-based component test should be added to verify accessibility coverage as required by the style guide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +144 to +209
<div class={'blind-header-content'}>
<ix-icon
class="collapse-icon"
name={iconChevronDownSmall}
color={
this.variant === 'filled' || this.variant === 'outline'
? 'color-std-text'
: `color-${this.variant}--contrast`
}
ref={(ref: HTMLElement | undefined) => (this.chevronRef = ref)}
></ix-icon>
<div
class="blind-header-title"
id={`ix-blind-header-title-${this.blindId}`}
>
{this.label !== undefined ? (
<Fragment>
{this.icon && (
<ix-icon
class="blind-header-title-icon"
name={this.icon}
color={
this.variant === 'filled' ||
this.variant === 'outline'
? 'color-std-text'
: `color-${this.variant}--contrast`
}
></ix-icon>
)}
<div class={'blind-header-title-row'}>
<div class="blind-header-title-col">
<ix-typography
title={this.label}
format="label-lg"
bold
>
{this.label}
</div>
</ix-typography>
</div>

{this.sublabel && (
<div class="blind-header-title-col">
<ix-typography title={this.sublabel}>
<div class="blind-header-title-sublabel">
{this.sublabel}
<div
class="blind-header-title-label"
title={this.label}
>
{this.label}
</div>
</ix-typography>
</div>
)}
</div>
<div class="header-actions">
<slot name="header-actions"></slot>
</div>
</Fragment>
) : null}

{this.sublabel && (
<div class="blind-header-title-col">
<ix-typography title={this.sublabel}>
<div class="blind-header-title-sublabel">
{this.sublabel}
</div>
</ix-typography>
</div>
)}
</div>
</Fragment>
) : null}
</div>
<div class="blind-header-custom">
<slot name="custom-header"></slot>
</div>
<div class="header-actions">
<slot name="header-actions"></slot>
</div>
</div>
</div>
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

Nested Interactive Controls (Accessibility Issue)

Wrapping the entire header content—including the custom-header and header-actions slots—inside the <button class="blind-header"> element creates nested interactive controls.

According to the HTML5 specification and WCAG 4.1.2, a <button> element must not contain any interactive descendants (such as other buttons, links, or form controls). Since consumers can slot arbitrary interactive elements (like <ix-icon-button> or custom links) into header-actions or custom-header, this nesting will break keyboard navigation and screen reader behavior.

Solution

Move the custom-header and header-actions slots outside of the <button> element, placing them as siblings to the button inside the .blind-header-wrapper flex container. This preserves the layout while ensuring semantic correctness and full accessibility.

            <div class="blind-header-content">
              <ix-icon
                class="collapse-icon"
                name={iconChevronDownSmall}
                color={
                  this.variant === 'filled' || this.variant === 'outline'
                    ? 'color-std-text'
                    : `color-${this.variant}--contrast`
                }
                ref={(ref: HTMLElement | undefined) => (this.chevronRef = ref)}
              ></ix-icon>
              <div
                class="blind-header-title"
                id={`ix-blind-header-title-${this.blindId}`}
              >
                {this.label !== undefined ? (
                  <Fragment>
                    {this.icon && (
                      <ix-icon
                        class="blind-header-title-icon"
                        name={this.icon}
                        color={
                          this.variant === 'filled' ||
                          this.variant === 'outline'
                            ? 'color-std-text'
                            : `color-${this.variant}--contrast`
                        }
                      ></ix-icon>
                    )}
                    <div class={'blind-header-title-row'}>
                      <div class="blind-header-title-col">
                        <ix-typography
                          title={this.label}
                          format="label-lg"
                          bold
                        >
                          <div
                            class="blind-header-title-label"
                            title={this.label}
                          >
                            {this.label}
                          </div>
                        </ix-typography>
                      </div>

                      {this.sublabel && (
                        <div class="blind-header-title-col">
                          <ix-typography title={this.sublabel}>
                            <div class="blind-header-title-sublabel">
                              {this.sublabel}
                            </div>
                          </ix-typography>
                        </div>
                      )}
                    </div>
                  </Fragment>
                ) : null}
              </div>
            </div>
          </button>
          <div class="blind-header-custom">
            <slot name="custom-header"></slot>
          </div>
          <div class="header-actions">
            <slot name="header-actions"></slot>
          </div>

min-height: 3rem;
height: 3rem;
width: calc(100% - 2 * var(--theme-blind--border-thickness));
width: 100%;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

To support moving the custom header and action slots outside of the <button> element (to avoid nested interactive controls), change the width of .blind-header from 100% to auto. This allows the flexbox layout of .blind-header-wrapper to distribute space correctly.

    width: auto;

Comment on lines 51 to 56
} @else {
.blind-header-title-label,
.blind-header-title-sublabel {
color: var(--theme-color-#{$variant}--contrast);
}

.header-actions {
--ix-icon-button-color: var(--theme-color-#{$variant}--contrast);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Loss of Icon Button Contrast in Colored Variants

Removing the .header-actions block that sets --ix-icon-button-color will cause icon buttons inside the header actions slot to lose their correct contrast color when using colored variants (such as alarm, critical, warning, etc.).

For example, on an alarm (red) background, the icon button should use the contrast color (--theme-color-alarm--contrast) to remain visible. Without this, it will default to a dark color, violating WCAG contrast ratio guidelines.

Please restore these custom property definitions so they apply to .header-actions under the respective variants.

    } @else {
      .blind-header-title-label,
      .blind-header-title-sublabel {
        color: var(--theme-color-#{$variant}--contrast);
      }

      .header-actions {
        --ix-icon-button-color: var(--theme-color-#{$variant}--contrast);
      }
    }

type="button"
aria-labelledby={`ix-blind-header-title-${this.blindId}`}
aria-controls={`ix-blind-content-section-${this.blindId}`}
aria-expanded={a11yBoolean(!this.collapsed)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

According to the Repository Style Guide (Accessibility section), component changes—especially those improving accessibility and focus handling—must have accessibility coverage verified with an axe-based component test.

Please add an axe-based accessibility test in packages/core/src/components/blind/test/blind.ct.ts following the pattern used in other components (e.g., using @axe-core/playwright or similar helper).

References
  1. For component changes, check that accessibility coverage exists with an axe-based component test. If an accessibility-relevant change lacks appropriate automated coverage, raise it as a review finding. (link)

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dfdecca8-5529-449e-a956-b03cca26e1ec

📥 Commits

Reviewing files that changed from the base of the PR and between 9acc431 and 6974499.

📒 Files selected for processing (1)
  • packages/core/src/components/blind/blind.tsx

📝 Walkthrough

Walkthrough

Updates blind.scss to adjust variant-based header colors and add a focus-visible outline, hides the header chevron icon from assistive technologies, and adds an Axe accessibility regression test plus a patch changeset note.

Changes

Blind accessibility improvements

Layer / File(s) Summary
Header contrast, focus, and icon semantics
packages/core/src/components/blind/blind.scss, packages/core/src/components/blind/blind.tsx
Updates variant-dependent header label, sublabel, and action icon colors; adds a focus-visible outline on .blind-header-wrapper; sets the header chevron icon to aria-hidden="true".
Axe accessibility regression test
packages/core/src/components/blind/test/blind.ct.ts, .changeset/upset-wings-exist.md
Adds an accessibility regression test that mounts ix-blind, runs Axe, and asserts no violations; updates the changeset with a patch note for the fix.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Suggested reviewers

  • danielleroux

Poem

Hop hop, the blind now shines so bright,
With focus rings and contrast right.
My bunny nose says, “all looks well,”
In accessible code, I leap and dwell. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding a visible focus outline to the blind header.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@SaiYugandhar03 SaiYugandhar03 marked this pull request as ready for review June 19, 2026 08:37

@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: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/core/src/components/blind/test/blind.ct.ts (1)

22-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a word-boundary regex for hydration class matching.

Line 22 should use \b boundaries to avoid partial class-name matches.

Suggested fix
-  await expect(blindElement).toHaveClass(/hydrated/);
+  await expect(blindElement).toHaveClass(/\bhydrated\b/);

As per coding guidelines, "Use word boundary \b in regex patterns to avoid false matches when checking classes like hydrated".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/blind/test/blind.ct.ts` at line 22, The regex
pattern in the expect statement for the blindElement class check is missing word
boundaries, which could cause false matches with class names like "not-hydrated"
or "pre-hydrated". Update the regex pattern from /hydrated/ to /\bhydrated\b/ to
add word-boundary anchors that ensure only the exact word "hydrated" is matched
and not as a partial substring within other class names.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/components/blind/blind.scss`:
- Line 55: In the blind.scss file, the else branch at line 55 uses `}else {`
which is missing the required `@` symbol for SCSS control structures. Change
`}else {` to `}`@else` {` to properly mark it as an SCSS conditional statement
rather than a CSS selector. This will ensure the else block is correctly parsed
as part of the conditional logic for the non-filled and non-outline variants.

In `@packages/core/src/components/blind/test/blind.ct.ts`:
- Around line 12-17: The regressionTest function is running the Axe scan via
analyze() immediately after mounting the ix-blind component, without verifying
that the component has been hydrated first. Add a hydration assertion between
the mount call and the analyze call using the pattern await
expect(page.locator('ix-blind')).toHaveClass(/\bhydrated\b/) to ensure the
component is fully hydrated before running accessibility checks.

---

Outside diff comments:
In `@packages/core/src/components/blind/test/blind.ct.ts`:
- Line 22: The regex pattern in the expect statement for the blindElement class
check is missing word boundaries, which could cause false matches with class
names like "not-hydrated" or "pre-hydrated". Update the regex pattern from
/hydrated/ to /\bhydrated\b/ to add word-boundary anchors that ensure only the
exact word "hydrated" is matched and not as a partial substring within other
class names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8c050eca-ee95-4032-a076-89c726d4ad67

📥 Commits

Reviewing files that changed from the base of the PR and between 5ad2fc5 and 1cef81a.

📒 Files selected for processing (2)
  • packages/core/src/components/blind/blind.scss
  • packages/core/src/components/blind/test/blind.ct.ts

Comment thread packages/core/src/components/blind/blind.scss Outdated
Comment thread packages/core/src/components/blind/test/blind.ct.ts Outdated

@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 platform limitations.

⚠️ Outside diff range comments (1)
packages/core/src/components/blind/test/blind.ct.ts (1)

22-22: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use word-boundary regex for hydration class checks.

Use \b around hydrated to avoid partial class-name matches.

As per coding guidelines, "Use word boundary \b in regex patterns to avoid false matches when checking classes like hydrated".

Suggested fix
-  await expect(blindElement).toHaveClass(/hydrated/);
+  await expect(blindElement).toHaveClass(/\bhydrated\b/);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/components/blind/test/blind.ct.ts` at line 22, The regex
pattern /hydrated/ in the toHaveClass expectation can match partial class names
like "not-hydrated" or "hydrated-component", causing false positives. Update the
pattern to /\bhydrated\b/ by adding word-boundary anchors around the word
"hydrated" to ensure only exact class-name matches are detected, following the
coding guidelines for regex pattern validation.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/components/blind/blind.scss`:
- Line 55: The closing brace and `@else` statement on line 55 in the blind.scss
file are not properly spaced, causing a Stylelint violation. Add a space between
the closing brace and the `@else` keyword in the `}`@else` {` syntax to make it `}
`@else` {` and satisfy the configured SCSS lint rule.

---

Outside diff comments:
In `@packages/core/src/components/blind/test/blind.ct.ts`:
- Line 22: The regex pattern /hydrated/ in the toHaveClass expectation can match
partial class names like "not-hydrated" or "hydrated-component", causing false
positives. Update the pattern to /\bhydrated\b/ by adding word-boundary anchors
around the word "hydrated" to ensure only exact class-name matches are detected,
following the coding guidelines for regex pattern validation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb43c989-520c-4930-bab6-8c4769032659

📥 Commits

Reviewing files that changed from the base of the PR and between 1cef81a and ab3da24.

📒 Files selected for processing (2)
  • packages/core/src/components/blind/blind.scss
  • packages/core/src/components/blind/test/blind.ct.ts

Comment thread packages/core/src/components/blind/blind.scss Outdated

@danielleroux danielleroux left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Following points are open:

  • Changeset is missing
  • Adapt pr title/description because there was no improvement to screenreader, there is only a update of the missing outline effect

@SaiYugandhar03 SaiYugandhar03 changed the title fix(core/blind): improve focus handling and screen reader accessibility fix(core/blind): add visible focus outline to blind header Jun 24, 2026
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants