Skip to content

Conversation

twjeffery
Copy link
Collaborator

Radio Component Style Updates - Issue #2936

Overview

Implements comprehensive style updates for the Radio component including new compact size variant, updated spacing, border
widths, colors, and improved accessibility compliance.

Changes Made

✅ New Features

  • Compact size variant: Added size="compact" prop to RadioGroup for tighter spacing
  • Updated spacing: Normal horizontal spacing increased from 24px to 32px, compact uses 24px
  • Enhanced border styling: Default borders increased from 1px to 1.5px, focus outline reduced from 3px to 2px
  • Improved color palette: Updated colors for better accessibility and visual hierarchy

✅ Component Updates

Cross-Framework Implementation

  • React: Updated RadioGroup wrapper with new size prop and proper TypeScript types
  • Angular: Updated RadioGroup wrapper with size prop support and attribute binding
  • Web Components: Core Svelte components updated with new styling and functionality

Visual Design Changes

  • Radio circle borders: Consistent 1.5px width across all states (except 2px focus outline)
  • Checked state redesign: New thin border + inner filled circle design (replacing thick border approach)
  • Focus positioning: 2px outline positioned 2px outside main border for better visibility
  • Label spacing: Normal size uses 12px gap, compact size uses 8px gap

✅ State-Specific Updates

Error States

  • Background colors: Default error #FDDED9, hover error #F4C8C5
  • Border colors: Error red #da291c, error hover #a91a10
  • All combinations: Error, error+hover, error+focus, error+checked variations

Disabled States

  • Border color: #BAB7B7 for all disabled states
  • Background color: #F2F0F0 for disabled radio circles
  • Inner circle: #BAB7B7 for disabled+selected state

Input Reveal Improvements

  • Border styling: Updated to 1px solid #E1DEDD (from 4px greyscale)
  • Content spacing: 25px gap between border line and revealed inputs
  • Better visual hierarchy: Subtler border integration

✅ Technical Implementation

Temporary CSS Overrides

  • Added comprehensive design token overrides in :host blocks for immediate testing
  • Includes fallback values ensuring components work without npm token dependencies
  • Will remain as permanent fallbacks when official tokens are published

Accessibility Enhancements

  • Focus indicators: 2px blue outline with 2px offset for better visibility
  • Color contrast: Updated colors meet WCAG AA standards
  • State clarity: Clear visual distinction between all interactive states

Backward Compatibility

  • No breaking changes: Existing usage continues to work unchanged
  • Default behavior: Components without size prop default to "normal" behavior
  • Graceful degradation: Fallback values ensure styling works in all scenarios

Files Modified

  • libs/common/src/lib/common.ts - Added RadioGroupSize type definition
  • libs/react-components/src/lib/radio-group/radio-group.tsx - React wrapper with size prop
  • libs/angular-components/src/lib/components/radio-group/radio-group.ts - Angular wrapper with size prop
  • libs/web-components/src/components/radio-group/RadioGroup.svelte - Group spacing and size inheritance
  • libs/web-components/src/components/radio-item/RadioItem.svelte - Core styling updates and temporary overrides

Testing

All variants have been tested in React playground including:

  • ✅ Normal vs compact size comparison
  • ✅ All state combinations (hover, focus, error, disabled, checked)
  • ✅ Backward compatibility (no size prop)
  • ✅ Input reveal functionality with updated styling
  • ✅ Cross-browser compatibility
  • ✅ Keyboard navigation and accessibility
import React, { useState } from "react";
import {
  GoabFilterChip,
  GoabGrid,
  GoabLink,
  GoabRadioGroup,
  GoabRadioItem,
  GoabBlock,
  GoabText,
  GoabOneColumnLayout,
  GoabPageBlock,
  GoabInput,
  GoabTextArea,
  GoabFormItem,
} from "@abgov/react-components";
import { Link } from "react-router-dom";
import { GoabRadioGroupOnChangeDetail } from "@abgov/ui-components-common";

export function Radio() {
  const [basicValue, setBasicValue] = useState<string>("");
  const [compactValue, setCompactValue] = useState<string>("");
  const [horizontalValue, setHorizontalValue] = useState<string>("option2");
  const [horizontalCompactValue, setHorizontalCompactValue] = useState<string>("option1");
  const [errorValue, setErrorValue] = useState<string>("");
  const [disabledValue, setDisabledValue] = useState<string>("option2");
  const [backcompatValue, setBackcompatValue] = useState<string>("");
  const [revealNormalValue, setRevealNormalValue] = useState<string>("");
  const [revealCompactValue, setRevealCompactValue] = useState<string>("");
  const [complexRevealValue, setComplexRevealValue] = useState<string>("");

  const handleChange =
    (setter: (value: string) => void) => (detail: GoabRadioGroupOnChangeDetail) => {
      setter(detail.value);
      console.log("Radio changed:", detail);
    };

  return (
    <GoabPageBlock width="full">
      <GoabOneColumnLayout>
        <GoabBlock direction="column" gap="l">
          <GoabLink mt={"xl"} leadingIcon={"arrow-back"}>
            <Link to="/">Back</Link>
          </GoabLink>

          <GoabText tag="h1" size="heading-l">
            Radio Component - Issue #2936 Testing
          </GoabText>

          <GoabText size="body-m" mb="l">
            Testing the Radio component updates for GitHub Issue #2936: new compact size
            variant, updated spacing (horizontal 24px→32px, radio gap 8px→12px), border
            widths (default 1px→1.5px, focus 3px→2px), and backward compatibility.
          </GoabText>

          {/* Test Case 1: Normal Size Vertical (Default Behavior) */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 1: Normal Size - Vertical Layout (Default)
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should show 16px gap between items, 12px label padding, 1.5px borders
          </GoabText>

          <GoabRadioGroup
            name="normal-vertical"
            value={basicValue}
            onChange={handleChange(setBasicValue)}
          >
            <GoabRadioItem value="option1" label="Normal vertical option 1" />
            <GoabRadioItem value="option2" label="Normal vertical option 2" />
            <GoabRadioItem value="option3" label="Normal vertical option 3" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {basicValue || "None"}
          </GoabText>

          {/* Test Case 2: Normal Size Horizontal */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 2: Normal Size - Horizontal Layout
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should show 32px gap between items (updated from 24px)
          </GoabText>

          <GoabRadioGroup
            name="normal-horizontal"
            orientation="horizontal"
            value={horizontalValue}
            onChange={handleChange(setHorizontalValue)}
          >
            <GoabRadioItem value="option1" label="Normal horizontal 1" />
            <GoabRadioItem value="option2" label="Normal horizontal 2" />
            <GoabRadioItem value="option3" label="Normal horizontal 3" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {horizontalValue}
          </GoabText>

          {/* Test Case 3: Compact Size Vertical */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 3: Compact Size - Vertical Layout
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should show 12px gap between items, 8px label padding
          </GoabText>

          <GoabRadioGroup
            name="compact-vertical"
            size="compact"
            value={compactValue}
            onChange={handleChange(setCompactValue)}
          >
            <GoabRadioItem value="option1" label="Compact vertical option 1" />
            <GoabRadioItem value="option2" label="Compact vertical option 2" />
            <GoabRadioItem value="option3" label="Compact vertical option 3" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {compactValue || "None"}
          </GoabText>

          {/* Test Case 4: Compact Size Horizontal */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 4: Compact Size - Horizontal Layout
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should show 24px gap between items, 8px label padding
          </GoabText>

          <GoabRadioGroup
            name="compact-horizontal"
            size="compact"
            orientation="horizontal"
            value={horizontalCompactValue}
            onChange={handleChange(setHorizontalCompactValue)}
          >
            <GoabRadioItem value="option1" label="Compact horizontal 1" />
            <GoabRadioItem value="option2" label="Compact horizontal 2" />
            <GoabRadioItem value="option3" label="Compact horizontal 3" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {horizontalCompactValue}
          </GoabText>

          {/* Test Case 5: Backward Compatibility - No size prop */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 5: Backward Compatibility - No Size Prop
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should default to "normal" behavior - no regression
          </GoabText>

          <GoabRadioGroup
            name="backward-compatibility"
            value={backcompatValue}
            onChange={handleChange(setBackcompatValue)}
          >
            <GoabRadioItem value="option1" label="No size prop option 1" />
            <GoabRadioItem value="option2" label="No size prop option 2" />
            <GoabRadioItem value="option3" label="No size prop option 3" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {backcompatValue || "None"}
          </GoabText>

          {/* Test Case 6: Error State with Normal Size */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 6: Error State - Normal Size
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should show error styling with updated border widths
          </GoabText>

          <GoabRadioGroup
            name="error-normal"
            error={true}
            value={errorValue}
            onChange={handleChange(setErrorValue)}
          >
            <GoabRadioItem value="option1" label="Error state normal option 1" />
            <GoabRadioItem value="option2" label="Error state normal option 2" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {errorValue || "None"}
          </GoabText>

          {/* Test Case 7: Error State with Compact Size */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 7: Error State - Compact Size
          </GoabText>
          <GoabText size="body-s" mb="m">
            Error styling with compact spacing
          </GoabText>

          <GoabRadioGroup
            name="error-compact"
            size="compact"
            error={true}
            value={errorValue}
            onChange={handleChange(setErrorValue)}
          >
            <GoabRadioItem value="option1" label="Error state compact option 1" />
            <GoabRadioItem value="option2" label="Error state compact option 2" />
          </GoabRadioGroup>

          {/* Test Case 8: Disabled State */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 8: Disabled State
          </GoabText>
          <GoabText size="body-s" mb="m">
            Should show disabled styling, option2 pre-selected
          </GoabText>

          <GoabRadioGroup
            name="disabled"
            value={disabledValue}
            disabled={true}
            onChange={handleChange(setDisabledValue)}
          >
            <GoabRadioItem value="option1" label="Disabled option 1" />
            <GoabRadioItem value="option2" label="Disabled option 2" />
            <GoabRadioItem value="option3" label="Disabled option 3" />
          </GoabRadioGroup>
          <GoabText size="body-s" mt="s">
            Selected: {disabledValue}
          </GoabText>

          {/* Test Case 9: Focus Test */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 9: Focus Test - Both Sizes
          </GoabText>
          <GoabText size="body-s" mb="m">
            Tab through these to test focus indicators (should be 2px outline, updated
            from 3px)
          </GoabText>

          <GoabBlock direction="column" gap="m">
            <GoabText tag="h3" size="heading-s">
              Normal Size Focus Test:
            </GoabText>
            <GoabRadioGroup name="focusNormal" orientation="horizontal">
              <GoabRadioItem value="focus1" label="Focus 1" />
              <GoabRadioItem value="focus2" label="Focus 2" />
            </GoabRadioGroup>

            <GoabText tag="h3" size="heading-s">
              Compact Size Focus Test:
            </GoabText>
            <GoabRadioGroup name="focusCompact" size="compact" orientation="horizontal">
              <GoabRadioItem value="focus1" label="Focus 1" />
              <GoabRadioItem value="focus2" label="Focus 2" />
            </GoabRadioGroup>
          </GoabBlock>

          {/* Test Case 10: Size Comparison Side by Side */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 10: Size Comparison - Side by Side
          </GoabText>

          <GoabBlock direction="row" gap="xl">
            <GoabBlock direction="column" gap="m">
              <GoabText tag="h3" size="heading-s">
                Normal Size
              </GoabText>
              <GoabRadioGroup name="compareNormal">
                <GoabRadioItem value="a" label="Option A" />
                <GoabRadioItem value="b" label="Option B" />
                <GoabRadioItem value="c" label="Option C" />
              </GoabRadioGroup>
            </GoabBlock>

            <GoabBlock direction="column" gap="m">
              <GoabText tag="h3" size="heading-s">
                Compact Size
              </GoabText>
              <GoabRadioGroup name="compareCompact" size="compact">
                <GoabRadioItem value="a" label="Option A" />
                <GoabRadioItem value="b" label="Option B" />
                <GoabRadioItem value="c" label="Option C" />
              </GoabRadioGroup>
            </GoabBlock>
          </GoabBlock>

          {/* Test Case 11: With Descriptions - Size Comparison */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 11: With Descriptions - Size Comparison
          </GoabText>
          <GoabText size="body-s" mb="m">
            Comparing descriptions with normal vs compact spacing
          </GoabText>

          <GoabBlock direction="row" gap="xl">
            <GoabBlock direction="column" gap="m">
              <GoabText tag="h3" size="heading-s">
                Normal Size with Descriptions
              </GoabText>
              <GoabRadioGroup name="descriptions-normal" size="normal">
                <GoabRadioItem
                  value="basic"
                  label="Basic Plan"
                  description="Free tier with basic features"
                />
                <GoabRadioItem
                  value="premium"
                  label="Premium Plan"
                  description="Full access with advanced features and priority support"
                />
                <GoabRadioItem
                  value="enterprise"
                  label="Enterprise Plan"
                  description="Custom solution for large organizations with dedicated support"
                />
              </GoabRadioGroup>
            </GoabBlock>

            <GoabBlock direction="column" gap="m">
              <GoabText tag="h3" size="heading-s">
                Compact Size with Descriptions
              </GoabText>
              <GoabRadioGroup name="descriptions-compact" size="compact">
                <GoabRadioItem
                  value="basic"
                  label="Basic Plan"
                  description="Free tier with basic features"
                />
                <GoabRadioItem
                  value="premium"
                  label="Premium Plan"
                  description="Full access with advanced features and priority support"
                />
                <GoabRadioItem
                  value="enterprise"
                  label="Enterprise Plan"
                  description="Custom solution for large organizations with dedicated support"
                />
              </GoabRadioGroup>
            </GoabBlock>
          </GoabBlock>

          {/* Test Case 12: Input Reveal Examples */}
          <GoabText tag="h2" size="heading-m" mt="2xl" mb="m">
            Test Case 12: Input Reveal - Normal Size
          </GoabText>
          <GoabText size="body-s" mb="m">
            Testing radio options that reveal additional input fields when selected
          </GoabText>

          <GoabRadioGroup 
            name="reveal-normal" 
            size="normal"
            value={revealNormalValue}
            onChange={handleChange(setRevealNormalValue)}
          >
            <GoabRadioItem value="option1" label="Standard option (no reveal)" />
            <GoabRadioItem 
              value="option2" 
              label="Email address"
              reveal={
                <GoabFormItem label="Email address" helpText="We'll use this to contact you about your application">
                  <GoabInput
                    name="email"
                    type="email"
                    placeholder="Enter your email address"
                    onChange={() => {/** do nothing */}}
                    value=""
                  />
                </GoabFormItem>
              }
            />
            <GoabRadioItem 
              value="option3" 
              label="Phone number"
              reveal={
                <GoabFormItem label="Phone number" helpText="Include area code">
                  <GoabInput
                    name="phone"
                    type="tel"
                    placeholder="(000) 000-0000"
                    onChange={() => {/** do nothing */}}
                    value=""
                  />
                </GoabFormItem>
              }
            />
            <GoabRadioItem 
              value="option4" 
              label="Other (please specify)"
              reveal={
                <GoabFormItem label="Please provide details">
                  <GoabTextArea
                    name="other-details"
                    placeholder="Describe your specific situation..."
                    rows={3}
                    onChange={() => {/** do nothing */}}
                    value=""
                  />
                </GoabFormItem>
              }
            />
          </GoabRadioGroup>

          {/* Test Case 13: Input Reveal Examples - Compact */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 13: Input Reveal - Compact Size
          </GoabText>
          <GoabText size="body-s" mb="m">
            Testing input reveals with compact spacing
          </GoabText>

          <GoabRadioGroup 
            name="reveal-compact" 
            size="compact"
            value={revealCompactValue}
            onChange={handleChange(setRevealCompactValue)}
          >
            <GoabRadioItem value="yes" label="Yes" />
            <GoabRadioItem 
              value="no" 
              label="No, please explain why"
              reveal={
                <GoabFormItem label="Explanation">
                  <GoabTextArea
                    name="explanation"
                    placeholder="Please explain why not..."
                    rows={2}
                    onChange={() => {/** do nothing */}}
                    value=""
                  />
                </GoabFormItem>
              }
            />
            <GoabRadioItem 
              value="maybe" 
              label="Maybe, depends on"
              reveal={
                <GoabFormItem label="Depends on what?">
                  <GoabInput
                    name="depends-on"
                    placeholder="What factors would influence your decision?"
                    onChange={() => {/** do nothing */}}
                    value=""
                  />
                </GoabFormItem>
              }
            />
          </GoabRadioGroup>

          {/* Test Case 14: Complex Input Reveal with Multiple Fields */}
          <GoabText tag="h2" size="heading-m" mt="xl" mb="m">
            Test Case 14: Complex Input Reveal - Multiple Fields
          </GoabText>
          <GoabText size="body-s" mb="m">
            Testing radio options that reveal multiple input fields
          </GoabText>

          <GoabRadioGroup 
            name="complex-reveal" 
            size="normal"
            value={complexRevealValue}
            onChange={handleChange(setComplexRevealValue)}
          >
            <GoabRadioItem value="individual" label="Individual applicant" />
            <GoabRadioItem 
              value="business" 
              label="Business applicant"
              reveal={
                <GoabBlock direction="column" gap="m">
                  <GoabFormItem label="Business name">
                    <GoabInput
                      name="business-name"
                      placeholder="Enter your business name"
                      onChange={() => {/** do nothing */}}
                      value=""
                    />
                  </GoabFormItem>
                  <GoabFormItem label="Business registration number">
                    <GoabInput
                      name="business-number"
                      placeholder="123456789"
                      onChange={() => {/** do nothing */}}
                      value=""
                    />
                  </GoabFormItem>
                  <GoabFormItem label="Primary contact person">
                    <GoabInput
                      name="contact-person"
                      placeholder="Full name of main contact"
                      onChange={() => {/** do nothing */}}
                      value=""
                    />
                  </GoabFormItem>
                </GoabBlock>
              }
            />
            <GoabRadioItem 
              value="organization" 
              label="Non-profit organization"
              reveal={
                <GoabBlock direction="column" gap="m">
                  <GoabFormItem label="Organization name">
                    <GoabInput
                      name="org-name"
                      placeholder="Enter your organization name"
                      onChange={() => {/** do nothing */}}
                      value=""
                    />
                  </GoabFormItem>
                  <GoabFormItem label="Charity registration number (if applicable)">
                    <GoabInput
                      name="charity-number"
                      placeholder="Optional - if you have one"
                      onChange={() => {/** do nothing */}}
                      value=""
                    />
                  </GoabFormItem>
                </GoabBlock>
              }
            />
          </GoabRadioGroup>

          {/* Test Summary and Checklist */}
          <GoabText tag="h2" size="heading-m" mt="2xl" mb="m">
            Issue #2936 - Test Summary & Checklist
          </GoabText>

          <GoabText size="body-m" mb="l">
            <strong>Manual Testing Instructions:</strong> Use keyboard navigation (Tab +
            Arrow keys) to test focus indicators, inspect element spacing with DevTools,
            and verify border widths.
          </GoabText>

          <GoabBlock direction="column" gap="s">
            <GoabText size="body-s">
              ✅ <strong>NEW FEATURE:</strong> size="compact" prop for tighter spacing
            </GoabText>
            <GoabText size="body-s">
              ✅ <strong>HORIZONTAL SPACING:</strong> Normal 32px (was 24px), Compact 24px
            </GoabText>
            <GoabText size="body-s">
              ✅ <strong>RADIO GAP:</strong> Normal 12px, Compact 8px label padding
            </GoabText>
            <GoabText size="body-s">
              ✅ <strong>BORDER WIDTHS:</strong> Default 1.5px (was 1px), Focus 2px (was
              3px)
            </GoabText>
            <GoabText size="body-s">
              ✅ <strong>BACKWARD COMPATIBILITY:</strong> No size prop defaults to
              "normal"
            </GoabText>
            <GoabText size="body-s">
              ✅ <strong>CROSS-FRAMEWORK:</strong> React, Angular, Web Components all
              updated
            </GoabText>
            <GoabText size="body-s">
              ✅ <strong>DESIGN TOKENS:</strong> Updated with CSS fallbacks
            </GoabText>
          </GoabBlock>

          <GoabText tag="h3" size="heading-s" mt="xl" mb="m">
            Manual Verification Steps:
          </GoabText>

          <GoabBlock direction="column" gap="xs">
            <GoabText size="body-s">
              1. Compare spacing differences between normal and compact variants above
            </GoabText>
            <GoabText size="body-s">
              2. Use Tab/Shift+Tab + Arrow keys to test focus indicators (should be 2px)
            </GoabText>
            <GoabText size="body-s">
              3. Inspect border widths in DevTools (1.5px default, 2px focus)
            </GoabText>
            <GoabText size="body-s">
              4. Verify error states show correct styling with updated borders
            </GoabText>
            <GoabText size="body-s">5. Test disabled states work as expected</GoabText>
            <GoabText size="body-s">
              6. Confirm no size prop defaults to normal behavior (backward compatibility)
            </GoabText>
            <GoabText size="body-s">
              7. Test with descriptions and mixed content lengths
            </GoabText>
          </GoabBlock>

          <GoabText tag="h3" size="heading-s" mt="xl" mb="m">
            Expected Design Token Values:
          </GoabText>

          <GoabBlock direction="column" gap="xs">
            <GoabText size="body-s">
              • --goa-radio-group-gap-horizontal: 32px (normal), 24px (compact)
            </GoabText>
            <GoabText size="body-s">
              • --goa-radio-group-gap-vertical: 16px (normal), 12px (compact)
            </GoabText>
            <GoabText size="body-s">
              • --goa-radio-gap: 12px (normal label padding)
            </GoabText>
            <GoabText size="body-s">
              • --goa-radio-gap-compact: 8px (compact label padding)
            </GoabText>
            <GoabText size="body-s">• --goa-radio-border: 1.5px solid (was 1px)</GoabText>
            <GoabText size="body-s">
              • --goa-radio-border-focus: 2px solid (was 3px)
            </GoabText>
          </GoabBlock>
        </GoabBlock>
      </GoabOneColumnLayout>
    </GoabPageBlock>
  );
}

Design Tokens

  • New tokens added for compact variant spacing
  • Updated token values for improved visual hierarchy
  • Temporary overrides provide immediate functionality
  • Official tokens will be published to design-tokens repository separately

Migration Guide

No migration required - all existing usage continues to work unchanged. New compact variant available via:

<GoabRadioGroup size="compact">

@twjeffery twjeffery changed the title feat(#2396): radio style update 2.0 feat(#2936): radio style update 2.0 Aug 4, 2025
@twjeffery twjeffery linked an issue Aug 4, 2025 that may be closed by this pull request
@twjeffery twjeffery added the 2.0 label Aug 4, 2025
@twjeffery twjeffery removed a link to an issue Oct 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant