Skip to content

Fleet UI: Button icon padding correction - #50195

Merged
RachelElysia merged 13 commits into
mainfrom
34377-button-icon-padding
Aug 3, 2026
Merged

Fleet UI: Button icon padding correction#50195
RachelElysia merged 13 commits into
mainfrom
34377-button-icon-padding

Conversation

@RachelElysia

@RachelElysia RachelElysia commented Jul 29, 2026

Copy link
Copy Markdown
Member

Issue

Closes #34377

Description

  • New Button icon API: single icon?: IconNames prop with optional iconPosition: "left" | "right" (default "left"). Icon-only buttons are derived from icon && no children — no more leftIcon / rightIcon / isIconOnly heuristic and no React.cloneElement acrobatics on icon children.
  • Icon rendering is centralized in Button: size derives from button size (16px default, 12px small), color derives from variant (core-fleet-white on default / alert / oversized; Icon default elsewhere). Callers stop passing size and color on icons.
  • Spacing (per Mike T design review 2026-07-29): default-size buttons keep their base 16px horizontal padding with an 8px gap between icon and label; small-size buttons use 8px horizontal padding with a 4px gap. Horizontal padding does NOT change based on icon presence or icon side. Applies uniformly across default, alert, secondary, subdued.
  • SCSS: single --with-icon class replaces --with-left-icon / --with-right-icon. Gap rules are scoped to the four icon-enabled variants, so the icon prop is a no-op on link, pill, unstyled, etc.
  • Two constants at the top of Button.tsx are the single source of truth for future variant tweaks: WHITE_TEXT_VARIANTS and ICON_ENABLED_VARIANTS.
  • Migrated ~74 call sites off the old leftIcon / rightIcon props and off the ad-hoc <Button><Icon .../></Button> child pattern. Sites that need a non-default icon color (e.g. TargetOption's green plus) or a className on the Icon (e.g. FeedListItem's close) intentionally stay on the child pattern.
  • Fills in previously-missing variant="default" size="small" and variant="alert" size="small" SCSS (no existing callers used those combos).

Screenrecording

  • Storybook comparison against Figma for each variant × size × icon-side combo

Testing

  • Added/updated automated tests
  • QA'd all new/changed functionality manually

Summary by CodeRabbit

  • New Features
    • Buttons now support built-in icons positioned before or after labels, as well as icon-only actions.
    • Added clearer accessible labels for icon-only controls across common workflows.
  • Style
    • Improved spacing, sizing, colors, and disabled states for buttons with icons.
    • Reduced spacing between pagination controls and refined dark-mode hover behavior.
  • Bug Fixes
    • Standardized button rendering to preserve consistent icon alignment and appearance across the application.
  • Tests
    • Expanded coverage for icon placement, variants, accessibility, sizing, loading, and legacy button content.

Copilot AI review requested due to automatic review settings July 29, 2026 23:18
@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.59459% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.21%. Comparing base (fd3da2f) to head (ac3eaa5).
⚠️ Report is 6 commits behind head on main.

Files with missing lines Patch % Lines
...erviceCategoriesPage/SelfServiceCategoriesPage.tsx 50.00% 1 Missing ⚠️
...policies/edit/components/PolicyForm/PolicyForm.tsx 0.00% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main   #50195   +/-   ##
=======================================
  Coverage   68.21%   68.21%           
=======================================
  Files        3944     3945    +1     
  Lines      251263   251284   +21     
  Branches    13446    13453    +7     
=======================================
+ Hits       171388   171404   +16     
- Misses      64525    64531    +6     
+ Partials    15350    15349    -1     
Flag Coverage Δ
frontend 61.32% <94.59%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI 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.

🟡 Not ready to approve

The new icon rendering/styling doesn’t fully align with the stated padding-trim goal and alert-variant icons currently render with incorrect (dark) default colors on a red background.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR updates Fleet’s Button component to support standardized icon+label rendering via new leftIcon/rightIcon props, and migrates many call sites to use those props so icon spacing is controlled in one place (matching the design-system intent in #34377).

Changes:

  • Added leftIcon / rightIcon props to frontend/components/buttons/Button/Button.tsx, including icon sizing by button size and new modifier classes.
  • Updated frontend/components/buttons/Button/_styles.scss to add icon+label spacing rules and to fill in missing default__small / alert__small styling.
  • Migrated numerous UI call sites to replace inline <Icon /> usage inside <Button> with leftIcon / rightIcon.
File summaries
File Description
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx Uses leftIcon for “Add package” button.
frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tsx Converts badge buttons to leftIcon.
frontend/pages/SoftwarePage/SoftwareLibrary/SoftwareLibraryTable/SoftwareLibraryTable.tsx Converts Categories control to leftIcon.
frontend/pages/SoftwarePage/SoftwareLibrary/SelfServiceCategoriesPage/SelfServiceCategoriesPage.tsx Converts “Add category” to leftIcon.
frontend/pages/SoftwarePage/SoftwareInventory/SoftwareInventoryTable/SoftwareInventoryTable.tsx Converts filter button to leftIcon.
frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx Converts “Show details” to leftIcon.
frontend/pages/SoftwarePage/components/forms/AdvancedOptionsFields/AdvancedOptionsFields.tsx Converts Schema action to rightIcon.
frontend/pages/SoftwarePage/components/cards/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx Converts “Edit” to leftIcon.
frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx Converts “Show query”/“Export” to rightIcon.
frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx Converts Schema/Live report actions to rightIcon.
frontend/pages/queries/details/QueryDetailsPage/QueryDetailsPage.tsx Converts Live report action to rightIcon.
frontend/pages/queries/details/components/QueryReport/QueryReport.tsx Converts export action to rightIcon.
frontend/pages/policies/edit/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx Converts “Add automations” to leftIcon.
frontend/pages/policies/edit/components/PolicyResults/PolicyResults.tsx Converts “Show query” to rightIcon.
frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tsx Converts Schema/Run policy actions to rightIcon.
frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx Converts Run policy action to rightIcon.
frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tsx Converts Reset policy action to rightIcon.
frontend/pages/policies/details/components/PolicyAutomationActivityDetailsModal/PolicyAutomationActivityDetailsModal.tsx Converts Reset policy action to leftIcon.
frontend/pages/ManageControlsPage/Variables/cards/GlobalVariables/GlobalVariables.tsx Converts “Add variable” to leftIcon.
frontend/pages/ManageControlsPage/Scripts/components/ScriptListHeading/ScriptListHeading.tsx Converts “Add script” to leftIcon.
frontend/pages/ManageControlsPage/components/UploadListHeading/UploadListHeading.tsx Converts create action to leftIcon.
frontend/pages/labels/NewLabelPage/NewLabelPage.tsx Converts Schema action to rightIcon.
frontend/pages/labels/components/DynamicLabelForm/DynamicLabelForm.tsx Converts Schema action to rightIcon.
frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx Converts multiple host actions to leftIcon.
frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsResendCell/OSSettingsResendCell.tsx Converts resend/rotate actions to leftIcon.
frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx Converts “Show query”/“Export results” to rightIcon.
frontend/pages/hosts/details/HostDetailsPage/modals/RecoveryLockPasswordModal/RecoveryLockPasswordModal.tsx Converts Rotate password action to leftIcon.
frontend/pages/hosts/details/HostDetailsPage/modals/ManagedAccountModal/ManagedAccountModal.tsx Converts Rotate password action to leftIcon.
frontend/pages/hosts/details/DeviceUserPage/components/InfoButton/InfoButton.tsx Converts Info action to rightIcon.
frontend/pages/hosts/details/cards/User/User.tsx Converts user actions to leftIcon/rightIcon.
frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tsx Converts installer action to leftIcon.
frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx Converts filter button to leftIcon.
frontend/pages/hosts/details/cards/Queries/HostQueries.tsx Converts “Add report” to leftIcon.
frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibraryTable/HostSoftwareLibraryTable.tsx Converts “Add software” to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/EntraTenantsListHeader/EntraTenantsListHeader.tsx Converts Add button to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/EntraClientIDsListHeader/EntraClientIDsListHeader.tsx Converts Add button to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MicrosoftEntraSection/WindowsAutomaticEnrollmentCard/WindowsEnrollmentCard.tsx Converts Edit CTA to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx Converts Edit CTA to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx Converts Edit CTA to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AndroidMdmCard/AndroidMdmCard.tsx Converts Edit CTA to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AppleBusinessManagerSection/VppCard/VppCard.tsx Converts Edit CTA to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AppleBusinessManagerSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx Converts Edit CTA to leftIcon.
frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx Converts download action to rightIcon.
frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListHeader/CertAuthorityListHeader.tsx Converts Add CA action to leftIcon.
frontend/components/queries/PackQueriesTable/PackQueriesTable.tsx Converts “Add report” empty-state CTA to rightIcon.
frontend/components/Pagination/Pagination.tsx Converts Previous button to leftIcon.
frontend/components/forms/LoginForm/LoginForm.tsx Converts Back button to leftIcon.
frontend/components/FleetsDropdown/FleetsDropdown.tsx Converts “Add fleet” to rightIcon.
frontend/components/EnrollSecrets/EnrollSecretModal/EnrollSecretModal.tsx Converts “Add secret” actions to leftIcon.
frontend/components/buttons/Button/Button.tsx Implements icon props, rendering, sizing, and modifier classes.
frontend/components/buttons/Button/Button.tests.tsx Adds tests for new icon props/classes and icon-only behavior interaction.
frontend/components/buttons/Button/Button.stories.tsx Updates stories to demonstrate leftIcon/rightIcon usage.
frontend/components/buttons/Button/_styles.scss Adds icon-spacing modifiers and fills missing small styles for default/alert.
frontend/components/BackButton/BackButton.tsx Converts back icon to leftIcon.
frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx Converts download actions to rightIcon.
Review details
  • Files reviewed: 55/55 changed files
  • Comments generated: 2
  • Review effort level: Low

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment on lines +170 to +171
// Default (green) buttons have white text — icons should match.
const iconColor = variant === "default" ? "core-fleet-white" : undefined;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixing

Comment on lines +333 to +337
&--with-left-icon,
&--with-right-icon {
padding-left: $pad-medium;
padding-right: $pad-medium;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

fixing

Copilot AI review requested due to automatic review settings July 31, 2026 14:21
@RachelElysia
RachelElysia force-pushed the 34377-button-icon-padding branch from 3dd5ad0 to d16ef21 Compare July 31, 2026 14:21

Copilot AI 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.

🟡 Not ready to approve

The new .button--with-left-icon/.button--with-right-icon SCSS rule is overly broad (affects unintended variants like link) and should be scoped to the intended variants before merging.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

frontend/components/buttons/Button/Button.tsx:58

  • The JSDoc for leftIcon/rightIcon says the icon-side padding is "trimmed", which reads like asymmetric padding. This PR (and the new SCSS) implements symmetric horizontal padding + a gap, so the wording is likely misleading for future callers.
  /** Renders an icon before the label with icon-side padding trimmed to match design. */
  leftIcon?: IconNames;
  /** Renders an icon after the label with icon-side padding trimmed to match design. */
  rightIcon?: IconNames;

frontend/components/buttons/Button/_styles.scss:341

  • The button--with-left-icon / button--with-right-icon rule currently applies to any button variant (including link, pill, grey-pill, etc.), but the comment says it should apply only to variants that carry an icon (default/alert/secondary/subdued). As written, using leftIcon/rightIcon on e.g. a link button would unexpectedly add 16px horizontal padding and change spacing.
  // Buttons with leftIcon / rightIcon use uniform 16px horizontal padding and
  // an 8px gap between icon and label at default size. Small buttons drop to
  // 8px horizontal padding and a 4px gap. Applies to all variants that carry
  // an icon (default, alert, secondary, subdued).
  &--with-left-icon,
  &--with-right-icon {
    padding-left: $pad-medium;
    padding-right: $pad-medium;

    .children-wrapper {
      gap: $pad-small;
    }
  }
  • Files reviewed: 53/53 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 14:34

Copilot AI 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.

🟡 Not ready to approve

The loading-state wrapper drops the children-wrapper class, which can cause size/layout shifts for icon+text buttons now using leftIcon/rightIcon.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

frontend/components/buttons/Button/Button.tsx:191

  • When isLoading is true, the wrapper div uses only transparent-text and loses the children-wrapper class, so the hidden content may lay out differently (e.g., block-level Icon wrappers stacking) and the button can change size while loading. Keeping children-wrapper applied preserves flex layout/gap and avoids layout shift for icon+text buttons using leftIcon/rightIcon.
        <div className={isLoading ? "transparent-text" : "children-wrapper"}>
          {leftIcon && (
            <Icon name={leftIcon} size={iconSize} color={iconColor} />
          )}
          {children}
          {rightIcon && (
            <Icon name={rightIcon} size={iconSize} color={iconColor} />
          )}
        </div>

frontend/components/buttons/Button/Button.tsx:58

  • The new leftIcon/rightIcon JSDoc says "icon-side padding trimmed", but the implementation keeps symmetric padding and only manages icon sizing/color + gap. Updating the comment avoids misleading future callers about padding behavior.
  /** Renders an icon before the label with icon-side padding trimmed to match design. */
  leftIcon?: IconNames;
  /** Renders an icon after the label with icon-side padding trimmed to match design. */
  rightIcon?: IconNames;
  • Files reviewed: 53/53 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 20:10

Copilot AI 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.

🟡 Not ready to approve

There are correctness and accessibility regressions (icon-only detection breaking legacy <Icon />-child usage and several new icon-only buttons missing ariaLabel) that should be addressed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (6)

frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageListItem/BootstrapPackageListItem.tsx:92

  • This delete action is an icon-only button; it needs an ariaLabel (or another accessible name) so assistive tech can identify it.
            <Button
              className={`${baseClass}__list-item-button`}
              variant="subdued"
              disabled={disabled}
              onClick={() => onDelete(bootstrapPackage)}
              icon="trash"
            />

frontend/components/buttons/Button/Button.tsx:183

  • The new icon-only detection only works for the icon prop. Existing icon-only buttons that render a lone <Icon /> child (often to control color/size), like the close buttons in frontend/components/Modal/Modal.tsx and frontend/components/AuthenticationNav/AuthenticationNav.tsx, will no longer get button--icon-only and will lose the square/centered styling. Also, icon is described in styles as a no-op for non-icon-enabled variants, but the current render logic will still insert the icon for any variant.
    const hasLabel = React.Children.count(children) > 0;
    // Square, centered layout for a bare icon on secondary/subdued — see #35329.
    const isIconOnly =
      !!icon && !hasLabel && (variant === "secondary" || variant === "subdued");
    const hasIconWithLabel =
      !!icon && hasLabel && ICON_ENABLED_VARIANTS.includes(variant!);
    const fullClassName = classnames(
      baseClass,
      `${baseClass}--${variant}`,
      className,
      {
        [`${baseClass}--${variant}__small`]: size === "small",
        [`${baseClass}__wide`]: size === "wide",
        [`${baseClass}--disabled`]: disabled,
        [`${baseClass}--icon-only`]: isIconOnly,
        [`${baseClass}--with-icon`]: hasIconWithLabel,
      }
    );
    // Variants with white text (dark backgrounds) — icons and the loading
    // spinner both render white so they're visible.
    const hasWhiteText = WHITE_TEXT_VARIANTS.includes(variant!);
    // Icons: 16px on default-size buttons, 12px on small-size buttons.
    const iconSize = size === "small" ? "small" : "medium";
    const iconColor = hasWhiteText ? "core-fleet-white" : undefined;
    const iconElement = icon && (
      <Icon name={icon} size={iconSize} color={iconColor} />
    );

frontend/components/buttons/Button/Button.stories.tsx:44

  • Template uses children: children ?? DEFAULT_ARGS.children, which treats null as “missing” and replaces it with the default label. That breaks the SecondaryIconOnlyVariant/SubduedIconOnlyVariant stories that intentionally pass null children to render an icon-only button.
const Template = (
  variant: ButtonVariant,
  children?: React.ReactNode,
  extraArgs?: Partial<IButtonProps> // e.g. { size: "small" } or { disabled: true }
): Story => ({
  args: {
    ...DEFAULT_ARGS,
    variant,
    children: children ?? DEFAULT_ARGS.children, // Fall back to default text
    ...extraArgs,
  },

frontend/pages/ManageControlsPage/SetupExperience/cards/SetupAssistant/components/SetupAssistantProfileCard/SetupAssistantProfileCard.tsx:93

  • These are icon-only buttons (no visible label). Without an ariaLabel (or an equivalent accessible name), screen readers will announce them as unlabeled buttons.
        <Button
          className={`${baseClass}__download-button`}
          variant="secondary"
          onClick={onDownload}
          icon="download"
        />
        {!props.defaultProfile && (
          <Button
            className={`${baseClass}__delete-button`}
            variant="secondary"
            onClick={props.onDelete}
            icon="trash"
          />

frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageListItem/BootstrapPackageListItem.tsx:47

  • This submit button is icon-only (no visible label). Add an ariaLabel so it has an accessible name for screen readers.

This issue also appears on line 86 of the same file.

      <Button
        variant="subdued"
        type="submit"
        className={`${baseClass}__list-item-button`}
        icon="download"
      />

frontend/pages/hosts/components/ScriptDetailsModal/ScriptDetailsModal.tsx:203

  • This download action is now an icon-only button with no accessible name. Add an ariaLabel so it’s announced correctly by screen readers.
  • Files reviewed: 73/73 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 20:28

Copilot AI 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.

🟡 Not ready to approve

There are correctness and accessibility issues to address (notably icon rendering on non-enabled variants and several icon-only buttons missing accessible labels), plus a Storybook template bug affecting icon-only stories.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (7)

frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx:225

  • This delete action renders as an icon-only button but has no ariaLabel/title, so it has no accessible name for screen readers.
                <Button
                  disabled={disableChildren}
                  className={`${subClass}__action-button`}
                  variant="secondary"
                  onClick={() => onClickDelete(profile)}
                  icon="trash"
                />

frontend/components/buttons/Button/Button.tsx:192

  • icon is rendered for any variant, but icon styling is intended to be limited to ICON_ENABLED_VARIANTS (per the constant name and the SCSS comment saying the icon prop is a no-op on link/pill/unstyled). As written, variant="pill" (and others) will still render the icon but without the button--with-icon class/gap, and it contradicts the documented behavior.
    const isIconOnly =
      (variant === "secondary" || variant === "subdued") &&
      ((!!icon && !hasLabel) || hasLoneIconChild);
    const hasIconWithLabel =
      !!icon && hasLabel && ICON_ENABLED_VARIANTS.includes(variant!);
    const fullClassName = classnames(
      baseClass,
      `${baseClass}--${variant}`,
      className,
      {
        [`${baseClass}--${variant}__small`]: size === "small",
        [`${baseClass}__wide`]: size === "wide",
        [`${baseClass}--disabled`]: disabled,
        [`${baseClass}--icon-only`]: isIconOnly,
        [`${baseClass}--with-icon`]: hasIconWithLabel,
      }
    );
    // Variants with white text (dark backgrounds) — icons and the loading
    // spinner both render white so they're visible.
    const hasWhiteText = WHITE_TEXT_VARIANTS.includes(variant!);
    // Icons: 16px on default-size buttons, 12px on small-size buttons.
    const iconSize = size === "small" ? "small" : "medium";
    const iconColor = hasWhiteText ? "core-fleet-white" : undefined;
    const iconElement = icon && (
      <Icon name={icon} size={iconSize} color={iconColor} />
    );

frontend/components/buttons/Button/Button.stories.tsx:43

  • Template uses children ?? DEFAULT_ARGS.children, but the icon-only stories pass children=null expecting to suppress the label. With ??, null falls back to the default label, so SecondaryIconOnlyVariant/SubduedIconOnlyVariant won’t actually be icon-only.
    ...DEFAULT_ARGS,
    variant,
    children: children ?? DEFAULT_ARGS.children, // Fall back to default text
    ...extraArgs,

frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx:197

  • This is an icon-only button (no children) but it does not provide an accessible name (ariaLabel or title). Icon-only controls should have an ariaLabel so screen readers can announce the action.

This issue also appears on line 219 of the same file.

          <Button
            className={`${subClass}__action-button`}
            variant="secondary"
            onClick={() => onClickInfo(profile)}
            icon="info"
          />

frontend/components/TargetsInput/TargetsInputHostsTableConfig.tsx:32

  • This is an icon-only remove button with no accessible name. Add an ariaLabel (or title) so assistive tech can announce what the control does.
          Header: "",
          Cell: (cellProps: ITableStringCellProps) => (
            <Button
              onClick={() => handleRowRemove(cellProps.row)}
              variant="subdued"
              icon="close-filled"
            />

frontend/pages/labels/components/ManualLabelForm/LabelHostTargetTableConfig.tsx:30

  • This icon-only remove button has no ariaLabel/title, so it lacks an accessible name.
    frontend/pages/admin/ManageUsersPage/components/ApiEndpointSelectorTable/ApiEndpointSelectorTable.tsx:128
  • This icon-only remove button lacks an accessible name. Add an ariaLabel (or title) so screen readers can identify the action.
  • Files reviewed: 73/73 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 21:16

Copilot AI 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.

🟡 Not ready to approve

Some icon-only buttons are missing accessible labels, and Button’s new spacing behavior doesn’t apply consistently to legacy <Icon>+label child patterns still used in the codebase.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (3)

frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx:225

  • This button is icon-only (no children), but it’s missing ariaLabel, so it won’t be announced meaningfully to screen readers. Please add an ariaLabel (e.g. Delete …) like other icon-only action buttons.
                <Button
                  disabled={disableChildren}
                  className={`${subClass}__action-button`}
                  variant="secondary"
                  onClick={() => onClickDelete(profile)}
                  icon="trash"
                />

frontend/components/buttons/Button/Button.tsx:171

  • button--with-icon is only applied when using the new icon prop, so legacy <Icon> child patterns with a text label (still used in the codebase for custom icon colors/classes) won’t get the small-size 4px gap override in _styles.scss and will keep the default 8px gap. Consider detecting the legacy "icon + label" child pattern and applying button--with-icon for it as well so spacing stays consistent regardless of API.
    const hasIconWithLabel =
      !!icon && hasLabel && ICON_ENABLED_VARIANTS.includes(variant!);

frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx:197

  • This button is icon-only (no children), but it’s missing ariaLabel, so screen readers will announce it as an unlabeled button. Please add an ariaLabel describing the action (consistent with the edit/download buttons below).

This issue also appears on line 219 of the same file.

          <Button
            className={`${subClass}__action-button`}
            variant="secondary"
            onClick={() => onClickInfo(profile)}
            icon="info"
          />
  • Files reviewed: 74/74 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings July 31, 2026 21:25

Copilot AI 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.

🟡 Not ready to approve

Some newly icon-only action buttons are missing ariaLabel, leaving them without an accessible name.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx:225

  • This delete action button is icon-only but currently has no ariaLabel. Add an ariaLabel so the control is accessible and its purpose is announced correctly by assistive tech.
                <Button
                  disabled={disableChildren}
                  className={`${subClass}__action-button`}
                  variant="secondary"
                  onClick={() => onClickDelete(profile)}
                  icon="trash"
                />

frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx:197

  • This button is now icon-only (no children). Please add an ariaLabel so it has an accessible name for screen readers and keyboard users.

This issue also appears on line 219 of the same file.

          <Button
            className={`${subClass}__action-button`}
            variant="secondary"
            onClick={() => onClickInfo(profile)}
            icon="info"
          />
  • Files reviewed: 74/74 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 3, 2026 14:35
@RachelElysia
RachelElysia force-pushed the 34377-button-icon-padding branch from a5827a4 to 6ef525a Compare August 3, 2026 14:35
- Remove unused --success variant and --justify-left/--justify-right
  offset classes from Button/_styles.scss (no callers, not in
  ButtonVariant).
- Trim the width-override selector in _global.scss to real classes
  (__wide, --icon-only); drop --icon / --icon__small which are never
  generated.
- Drop dark-mode card/modal hover selectors for --inverse,
  --inverse-alert, --text-icon, --icon (variants don't exist); keep
  --secondary / --subdued.

Copilot AI 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.

🟡 Human review recommended

It updates a core shared UI component and migrates many call sites, so a final human review/UX regression pass is needed to confirm no visual or accessibility regressions.

Review details
  • Files reviewed: 80/80 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Copilot AI review requested due to automatic review settings August 3, 2026 14:44

Copilot AI 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.

🟡 Not ready to approve

The Button icon-only path still allows unlabeled icon-only buttons (accessibility), and test coverage doesn’t yet assert the new white-text icon coloring for alert/oversized variants.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (2)

frontend/components/buttons/Button/Button.tsx:220

  • Icon-only buttons rely on ariaLabel for an accessible name, but aria-label is currently set only from ariaLabel. If a caller forgets ariaLabel but does set title, screen readers will still have no label. Consider falling back to title when the button is icon-only to avoid silent accessibility regressions.
        aria-haspopup={ariaHasPopup}
        aria-expanded={ariaExpanded}
        aria-label={ariaLabel}
        aria-pressed={ariaPressed}

frontend/components/buttons/Button/Button.tests.tsx:137

  • The icon auto-coloring logic now treats alert and oversized as white-text variants (via WHITE_TEXT_VARIANTS), but the tests only assert the white icon color for variant="default". Adding an alert (and/or oversized) assertion would prevent regressions like the one previously reported for alert buttons.
  it("auto-colors the icon white on white-text variants (default)", () => {
    render(
      <Button variant="default" icon="plus">
        Add
      </Button>
    );
    expect(
      screen
        .getByTestId("plus-icon")
        .querySelector("path")
        ?.getAttribute("stroke")
    ).toContain("core-fleet-white");
  });
  • Files reviewed: 80/80 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

ariaLabel,
ariaPressed,
size,
icon,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

long awaited refactor into a prop

The prop enforces consistency that the child pattern couldn't:

  1. Spacing — Button owns the 8px/4px gap via .button--with-icon. As a child, every caller wrapped the label in (or forgot) and drift
    accumulated.
  2. Sizing — 16px on default, 12px on small, derived from size. As a child, callers had to pass size="small" on Icon separately. Migration
    audit found ~24 sites where the child Icon was size="small" on a default-size button — a visual bug.
  3. Color — auto-mapped from variant (white on dark-fill, ui-fleet-black-75 on secondary/subdued). As a child, callers had to remember the
    right color per variant. Multiple bugs were fixed by this PR alone (e.g., alert button rendering a dark icon that vanished into the red fill).
  4. Icon-only detection — with the prop + no children, Button knows to apply square 36/28px styling. As a child, the same could mean
    "icon-only" or "icon that happens to be alone right now" depending on siblings.
  5. Type safety — icon: IconNames is a constrained union. A child compiles fine.
  6. Non-icon variants no-op cleanly — a link or pill variant with icon="foo" drops the render (per our recent change). A child would
    render unspaced garbage.

Escape hatch preserved: the legacy -child pattern still triggers icon-only square styling. So sites that legitimately need a
non-standard color or className on the icon (rare — mostly TargetOption's green plus, FeedListItem etc.) can keep the child pattern.

Short version: "same visual output, less caller responsibility." Every property the icon needs to look right (size, color, gap, position) can
be derived from the Button's own props. Making the caller specify them again invites drift.

- Console.warn (dev only) when an icon-only Button has neither
  ariaLabel nor title, so unlabeled icon-only buttons surface at
  runtime instead of shipping as a11y regressions. Browsers already
  fall back to title for the accessible name, so either prop
  satisfies a screen reader.
- Clarify WHITE_TEXT_VARIANTS comment: oversized is listed for the
  loading spinner, not icons (not in ICON_ENABLED_VARIANTS).
- Add tests: alert renders icon in white; oversized drops the icon;
  the new dev warning fires on missing labels but not when title is
  present.
Copilot AI review requested due to automatic review settings August 3, 2026 15:40
right: -$pad-small;
margin-left: -$pad-small;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

old, unused code

@RachelElysia
RachelElysia marked this pull request as ready for review August 3, 2026 15:42
@RachelElysia
RachelElysia requested a review from a team as a code owner August 3, 2026 15:42

Copilot AI 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.

🟢 Ready to approve

Only a minor maintainability nit was found (dev warning could be more actionable for legacy icon-only buttons), and the functional/icon API changes appear consistent and well-covered by updated tests.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Review details

Suppressed comments (1)

frontend/components/buttons/Button/Button.tsx:224

  • The dev-only accessibility warning will log icon="unknown" for legacy icon-only buttons that use the <Icon /> child pattern (since icon prop is unset). That makes it harder to locate the offending button in large pages; consider deriving the icon name from the lone <Icon> child when present.
      console.warn(
        `Icon-only Button (icon="${
          icon ?? "unknown"
        }") has no ariaLabel or title — ` +
          `screen readers will announce it as an unlabeled button.`
  • Files reviewed: 80/80 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

The shared Button component now accepts icon names and positions, supports icon-only states, applies variant-specific icon styling, and warns when icon-only buttons lack accessible names. Tests and Storybook examples cover the new behavior. Frontend components across hosts, controls, software, administration, policies, and queries now use Button icon props instead of nested Icon elements. Global button styling and pagination spacing were updated.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: correcting Fleet UI button icon padding.
Description check ✅ Passed The description identifies issue #34377, explains the implementation, and records automated testing and manual QA results.
Linked Issues check ✅ Passed The changes implement the linked issue’s uniform icon sizing, symmetric padding, and icon spacing for the required button variants [#34377].
Out of Scope Changes check ✅ Passed The call-site migrations, accessibility updates, tests, and style cleanup directly support the unified Button icon API and linked issue objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 34377-button-icon-padding

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.

@coderabbitai coderabbitai 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.

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)
frontend/components/buttons/Button/_styles.scss (1)

294-309: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Add the 16px horizontal padding override to &--subdued.

button-variant(..., $inverse: true) applies the 8px padding mixin, and &--secondary overrides it with button-pad-8px-16px; &--subdued does not. Add that override to keep subdued aligned with the default/alert/secondary full-size padding spec.

🤖 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 `@frontend/components/buttons/Button/_styles.scss` around lines 294 - 309,
Update the &--subdued style to apply the button-pad-8px-16px horizontal padding
override after button-variant, matching the padding behavior of the default,
alert, and secondary full-size button variants.
🤖 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 `@frontend/components/buttons/Button/Button.tsx`:
- Around line 156-176: Update the accessibility warning condition near the
Button render logic to warn whenever an icon is rendered without a label on any
variant in ICON_ENABLED_VARIANTS, including default and alert, while preserving
the existing ariaLabel and title exemptions. Add or update Button.tests.tsx
coverage for an unlabeled default or alert icon button.

In `@frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx`:
- Around line 92-94: Update the light- and dark-mode action buttons in LogoCard
to include the respective mode in each ariaLabel, making both “Replace” and
“Remove” actions uniquely identify whether they target the light or dark logo.

---

Outside diff comments:
In `@frontend/components/buttons/Button/_styles.scss`:
- Around line 294-309: Update the &--subdued style to apply the
button-pad-8px-16px horizontal padding override after button-variant, matching
the padding behavior of the default, alert, and secondary full-size button
variants.
🪄 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: CHILL

Plan: Pro Plus

Run ID: 23aa501d-5578-4d51-b1f9-bad7335dce9d

📥 Commits

Reviewing files that changed from the base of the PR and between a586a53 and ac3eaa5.

📒 Files selected for processing (80)
  • frontend/components/AddHostsModal/PlatformWrapper/PlatformWrapper.tsx
  • frontend/components/BackButton/BackButton.tsx
  • frontend/components/EnrollSecrets/EnrollSecretModal/EnrollSecretModal.tsx
  • frontend/components/EnrollSecrets/EnrollSecretTable/EnrollSecretRow/EnrollSecretRow.tsx
  • frontend/components/FeedListItem/FeedListItem.tsx
  • frontend/components/FleetsDropdown/FleetsDropdown.tsx
  • frontend/components/Pagination/Pagination.tsx
  • frontend/components/Pagination/_styles.scss
  • frontend/components/TargetsInput/TargetsInputHostsTableConfig.tsx
  • frontend/components/buttons/Button/Button.stories.tsx
  • frontend/components/buttons/Button/Button.tests.tsx
  • frontend/components/buttons/Button/Button.tsx
  • frontend/components/buttons/Button/_styles.scss
  • frontend/components/forms/LoginForm/LoginForm.tsx
  • frontend/components/forms/fields/InputField/InputField.tsx
  • frontend/components/queries/PackQueriesTable/PackQueriesTable.tsx
  • frontend/pages/ManageControlsPage/OSSettings/cards/Certificates/Certificates.tsx
  • frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/ConfigurationProfiles.tsx
  • frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetListItem/AssetListItem.tsx
  • frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/AssetsTab/AssetsTab.tsx
  • frontend/pages/ManageControlsPage/OSSettings/cards/ConfigurationProfiles/components/ProfileListItem/ProfileListItem.tsx
  • frontend/pages/ManageControlsPage/Scripts/cards/ScriptLibrary/ScriptLibrary.tsx
  • frontend/pages/ManageControlsPage/Scripts/components/ScriptListItem/ScriptListItem.tsx
  • frontend/pages/ManageControlsPage/SetupExperience/cards/BootstrapPackage/components/BootstrapPackageListItem/BootstrapPackageListItem.tsx
  • frontend/pages/ManageControlsPage/SetupExperience/cards/RunScript/components/SetupExperienceScriptCard/SetupExperienceScriptCard.tsx
  • frontend/pages/ManageControlsPage/SetupExperience/cards/SetupAssistant/components/SetupAssistantProfileCard/SetupAssistantProfileCard.tsx
  • frontend/pages/ManageControlsPage/Variables/cards/CustomHostVitalsTab/CustomHostVitalsTab.tsx
  • frontend/pages/ManageControlsPage/Variables/cards/GlobalVariables/GlobalVariables.tsx
  • frontend/pages/SoftwarePage/SoftwareAddPage/SoftwareFleetMaintained/FleetMaintainedAppDetailsPage/FleetMaintainedAppDetailsPage.tsx
  • frontend/pages/SoftwarePage/SoftwareInventory/SoftwareInventoryTable/SoftwareInventoryTable.tsx
  • frontend/pages/SoftwarePage/SoftwareLibrary/SelfServiceCategoriesPage/SelfServiceCategoriesPage.tsx
  • frontend/pages/SoftwarePage/SoftwareLibrary/SoftwareLibraryTable/SoftwareLibraryTable.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/LibraryItemAccordion/LibraryItemAccordion.tsx
  • frontend/pages/SoftwarePage/SoftwareTitleDetailsPage/SoftwareTitleDetailsPage.tsx
  • frontend/pages/SoftwarePage/components/cards/SoftwareDetailsSummary/SoftwareDetailsSummary.tsx
  • frontend/pages/SoftwarePage/components/forms/AdvancedOptionsFields/AdvancedOptionsFields.tsx
  • frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListHeader/CertAuthorityListHeader.tsx
  • frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CertAuthorityListItem/CertAuthorityListItem.tsx
  • frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/ConditionalAccess.tsx
  • frontend/pages/admin/IntegrationsPage/cards/ConditionalAccess/components/OktaConditionalAccessModal/OktaConditionalAccessModal.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/EntraClientIDsListHeader/EntraClientIDsListHeader.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/EntraClientIDsListItem/EntraClientIDsListItem.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/EntraTenantsListHeader/EntraTenantsListHeader.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/WindowsAutomaticEnrollmentPage/EntraTenantsListItem/EntraTenantsListItem.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AppleBusinessManagerSection/AppleAutomaticEnrollmentCard/AppleAutomaticEnrollmentCard.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/AppleBusinessManagerSection/VppCard/VppCard.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AndroidMdmCard/AndroidMdmCard.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/AppleMdmCard/AppleMdmCard.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MdmSettingsSection/WindowsMdmCard/WindowsMdmCard.tsx
  • frontend/pages/admin/IntegrationsPage/cards/MdmSettings/components/MicrosoftEntraSection/WindowsAutomaticEnrollmentCard/WindowsEnrollmentCard.tsx
  • frontend/pages/admin/ManageUsersPage/components/ApiEndpointSelectorTable/ApiEndpointSelectorTable.tsx
  • frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx
  • frontend/pages/hosts/ManageHostsPage/ManageHostsPage.tsx
  • frontend/pages/hosts/ManageHostsPage/components/CustomLabelGroupHeading/CustomLabelGroupHeading.tsx
  • frontend/pages/hosts/ManageHostsPage/components/HostsFilterBlock/HostsFilterBlock.tsx
  • frontend/pages/hosts/components/ScriptDetailsModal/ScriptDetailsModal.tsx
  • frontend/pages/hosts/details/DeviceUserPage/components/InfoButton/InfoButton.tsx
  • frontend/pages/hosts/details/HostDetailsPage/modals/ManagedAccountModal/ManagedAccountModal.tsx
  • frontend/pages/hosts/details/HostDetailsPage/modals/RecoveryLockPasswordModal/RecoveryLockPasswordModal.tsx
  • frontend/pages/hosts/details/HostQueryReport/HQRTable/HQRTable.tsx
  • frontend/pages/hosts/details/OSSettingsModal/OSSettingsTable/OSSettingsResendCell/OSSettingsResendCell.tsx
  • frontend/pages/hosts/details/cards/HostSoftwareLibrary/HostSoftwareLibraryTable/HostSoftwareLibraryTable.tsx
  • frontend/pages/hosts/details/cards/Queries/HostQueries.tsx
  • frontend/pages/hosts/details/cards/Software/HostSoftwareTable/HostSoftwareTable.tsx
  • frontend/pages/hosts/details/cards/Software/SelfService/components/UpdatesCard/UpdateSoftwareItem/UpdateSoftwareItem.tsx
  • frontend/pages/hosts/details/cards/User/User.tsx
  • frontend/pages/labels/NewLabelPage/NewLabelPage.tsx
  • frontend/pages/labels/components/DynamicLabelForm/DynamicLabelForm.tsx
  • frontend/pages/labels/components/ManualLabelForm/LabelHostTargetTableConfig.tsx
  • frontend/pages/policies/details/PolicyDetailsPage/PolicyDetailsPage.tsx
  • frontend/pages/policies/details/components/PolicyAutomationActivityDetailsModal/PolicyAutomationActivityDetailsModal.tsx
  • frontend/pages/policies/details/components/PolicyAutomationsActivitiesTable/PolicyAutomationsActivitiesTable.tsx
  • frontend/pages/policies/edit/components/PolicyForm/PolicyForm.tsx
  • frontend/pages/policies/edit/components/PolicyResults/PolicyResults.tsx
  • frontend/pages/policies/edit/components/SaveNewPolicyModal/SaveNewPolicyModal.tsx
  • frontend/pages/queries/details/QueryDetailsPage/QueryDetailsPage.tsx
  • frontend/pages/queries/details/components/QueryReport/QueryReport.tsx
  • frontend/pages/queries/edit/components/EditQueryForm/EditQueryForm.tsx
  • frontend/pages/queries/edit/components/QueryResults/QueryResults.tsx
  • frontend/styles/global/_global.scss

Comment on lines +156 to +176
icon,
iconPosition,
} = this.props;
// The bordered "secondary" and borderless "subdued" variants render as a
// square when their only content is an icon (no text label) — see #35329.
// toArray strips false/null and flattens fragments, so we reliably detect a
// lone <Icon> child while ignoring conditional or wrapped text content.
// Square, centered layout for a bare icon on secondary/subdued — see #35329.
// Detects both the modern `icon` prop and the legacy `<Icon>` child pattern
// used by call sites that need a color/className override on the icon.
// `toArray` (not `Children.count`) so `{cond && "text"}` with a false `cond`
// reads as "no label" — otherwise the false child still counts as a label
// and the visually-lone icon skips its square styling.
const childArray = React.Children.toArray(children);
const isIconOnly =
(variant === "secondary" || variant === "subdued") &&
const hasLabel = childArray.length > 0;
const hasLoneIconChild =
!icon &&
childArray.length === 1 &&
React.isValidElement(childArray[0]) &&
childArray[0].type === Icon;
const isIconOnly =
(variant === "secondary" || variant === "subdued") &&
((!!icon && !hasLabel) || hasLoneIconChild);
const hasIconWithLabel =
!!icon && hasLabel && ICON_ENABLED_VARIANTS.includes(variant!);

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Extend the dev accessibility warning to cover icon-only default/alert buttons.

ICON_ENABLED_VARIANTS includes default and alert, so icon renders on those variants even without children (Line 205). But isIconOnly at Line 172 only becomes true for secondary/subdued, and the warning at Line 211 fires only isIconOnly && !ariaLabel && !title. A <Button variant="default" icon="trash" /> with no ariaLabel or title renders an icon with no accessible name and produces no console warning, unlike the equivalent secondary/subdued case. Button.tests.tsx has no test for this scenario, so this path is unverified.

Broaden the warning condition to also catch icon-without-label on any icon-enabled variant, not only the styled icon-only ones.

🩹 Proposed fix
     if (
       process.env.NODE_ENV !== "production" &&
-      isIconOnly &&
+      (isIconOnly ||
+        (!!icon && !hasLabel && ICON_ENABLED_VARIANTS.includes(variant!))) &&
       !ariaLabel &&
       !title
     ) {

Also applies to: 186-224

🤖 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 `@frontend/components/buttons/Button/Button.tsx` around lines 156 - 176, Update
the accessibility warning condition near the Button render logic to warn
whenever an icon is rendered without a label on any variant in
ICON_ENABLED_VARIANTS, including default and alert, while preserving the
existing ariaLabel and title exemptions. Add or update Button.tests.tsx coverage
for an unlabeled default or alert icon button.

Comment on lines +92 to +94
icon="pencil"
ariaLabel="Replace logo"
/>

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the logo action names unique per mode.

LogoCard renders separate light-mode and dark-mode actions, but both buttons expose Replace logo and Remove logo. The card label is not associated with the button. A screen reader user cannot identify which logo the action targets.

Use mode in each ariaLabel.

Proposed fix
-                ariaLabel="Replace logo"
+                ariaLabel={`Replace ${mode} mode logo`}
...
-                ariaLabel="Remove logo"
+                ariaLabel={`Remove ${mode} mode logo`}

Also applies to: 106-108

🤖 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 `@frontend/pages/admin/OrgSettingsPage/cards/Info/Info.tsx` around lines 92 -
94, Update the light- and dark-mode action buttons in LogoCard to include the
respective mode in each ariaLabel, making both “Replace” and “Remove” actions
uniquely identify whether they target the light or dark logo.

@RachelElysia
RachelElysia merged commit 756295a into main Aug 3, 2026
27 checks passed
@RachelElysia
RachelElysia deleted the 34377-button-icon-padding branch August 3, 2026 18:10
RachelElysia added a commit that referenced this pull request Aug 3, 2026
Migrate the toast's chevron and close buttons off the child <Icon>
pattern to the new icon prop from #50195. Drop the explicit
ui-fleet-black-75 color on close (subdued's default matches) and move
the chevron rotation className from the Icon wrapper up to the Button
so the existing __chevron / __chevron--open selectors still resolve.

Also add two tests covering aria-controls on Button — attribute present
when ariaControls is provided, omitted when undefined.
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.

UI Improvement: Button icon padding correction

3 participants