Repository: SU-SWS/ood-giving-campaign
Pull request #414: A11y | DS-1508 | Caption update; Initiative Card CTA fixup
Author: login: yvonnetangsu
Created: 2025-04-30T18:57:47Z | Merged: 2025-05-07T21:40:30Z

# READY FOR REVIEW

# Summary
- Caption related a11y update and other fixups. Other than removing the `aria-describedby` in the story hero images, previously I created a reusable `Caption` component which uses the `figcaption` element as the wrapper. I ended up using it in components that are not `figure` elements but forgot that I need to provide an option so the Caption wrapper is a div instead of a figcaption. This PR fixes that.
- Initiative Card CTA fixup

# Review By (Date)
- Soon

# Criticality
- 5

# Review Tasks

## Setup tasks and/or behavior to test

1. Go to https://deploy-preview-414--giving-campaign.netlify.app/stories/outrunning-the-next-pandemic
2. Check that in the story hero, both the foreground and background images don't have an `aria-describedby` attribute anymore
3. Inspect the caption below the story hero, check that it's a `div` and not a `figcaption`
4. GO to https://deploy-preview-414--giving-campaign.netlify.app/stories/education-for-a-life-of-purpose
5. Inspect the featured masonry component
![Screenshot 2025-05-07 at 1 57 23 PM](https://github.com/user-attachments/assets/a989e17f-df05-4bed-ad81-36ec7114af4e)
6. Check that its wrapper is a html `figure`, and that its caption is a `figcaption`.
7. Scroll further down to below a scrollytelling section
8. Inspect the caption - it should be a `div`, not a `figcaption`
9. Scroll down to a media player, inspect the code - the wrapper should be a `figure` and the caption should be a `figcaption`.
![Screenshot 2025-05-07 at 2 00 29 PM](https://github.com/user-attachments/assets/718e3fd9-f3f5-4a56-a1d0-f5e1e692161a)
10. Go to https://deploy-preview-414--giving-campaign.netlify.app/stories/clearing-the-air and scroll down to a Section, inspect the caption below - it should be a `div`
![Screenshot 2025-05-07 at 2 02 57 PM](https://github.com/user-attachments/assets/9c3cb1b4-d314-4d22-b075-95d5a4cfe051)
11. Scroll down to an Image Annotation component, the wrapper around the whole component should be a `figure`, and the caption should be a `figcaption`.
![Screenshot 2025-05-07 at 2 03 33 PM](https://github.com/user-attachments/assets/ea3b335a-48e4-4eb7-a8e2-e8629c8774e5)
12. Go to the Initiative page. 
https://deploy-preview-414--giving-campaign.netlify.app/initiatives
13. Hover/focus on an initiative card. The CTA link should only have the underline on the last word and the underline should turn to white on hover and keyboard focus.
![Screenshot 2025-05-07 at 2 04 53 PM](https://github.com/user-attachments/assets/e7100ced-440e-4bd0-9ccd-38a3500f0725)


# Associated Issues and/or People
- JIRA ticket(s) - DS-1508


Diff:
diff --git a/components/BlurryPoster/BlurryPoster.tsx b/components/BlurryPoster/BlurryPoster.tsx
index 3a88875c..e90963fc 100644
--- a/components/BlurryPoster/BlurryPoster.tsx
+++ b/components/BlurryPoster/BlurryPoster.tsx
@@ -60,10 +60,6 @@ type BlurryPosterProps = React.HTMLAttributes<HTMLDivElement> & {
   alt?: string;
   tabColor?: AccentColorType;
   cta?: React.ReactNode;
-  /**
-   * aria-describedby will be added to the images if a caption exists
-   */
-  hasCaption?: boolean;
 };
 
 export const BlurryPoster = ({
@@ -97,7 +93,6 @@ export const BlurryPoster = ({
   alt,
   tabColor,
   cta,
-  hasCaption,
   className,
   ...props
 }: BlurryPosterProps) => {
@@ -191,7 +186,6 @@ export const BlurryPoster = ({
             alt={bgImageAlt || ''}
             width={2000}
             height={1200}
-            aria-describedby={hasCaption && !!bgImageAlt ? 'story-hero-caption' : undefined}
             className={styles.bgImage}
             fetchPriority={type === 'hero' ? 'high' : 'auto'}
           />
@@ -351,7 +345,6 @@ export const BlurryPoster = ({
                       alt={alt || ''}
                       width={type === 'hero' && !isTwoCol ? 1800 : 750}
                       height={type === 'hero' && !isTwoCol ? 900 : 1000}
-                      aria-describedby={hasCaption && !!alt ? 'story-hero-caption' : undefined}
                       fetchPriority={type === 'hero' ? 'high' : 'auto'}
                       className={styles.image}
                     />
diff --git a/components/Container/Container.types.ts b/components/Container/Container.types.ts
index 7d9a358d..4b7d6d05 100644
--- a/components/Container/Container.types.ts
+++ b/components/Container/Container.types.ts
@@ -1,6 +1,6 @@
 import * as styles from './Container.styles';
 
-export type ContainerElementType = 'div' | 'section' | 'article' | 'main' | 'footer' | 'aside' | 'header' | 'nav' | 'form' | 'fieldset' | 'figcaption';
+export type ContainerElementType = 'div' | 'section' | 'article' | 'main' | 'footer' | 'aside' | 'header' | 'nav' | 'form' | 'fieldset' | 'figcaption' | 'figure';
 
 export type WidthType = keyof typeof styles.widths;
 
diff --git a/components/EmbedMedia/EmbedMedia.styles.ts b/components/EmbedMedia/EmbedMedia.styles.ts
index 2e894312..e1cfeeee 100644
--- a/components/EmbedMedia/EmbedMedia.styles.ts
+++ b/components/EmbedMedia/EmbedMedia.styles.ts
@@ -2,4 +2,3 @@ export const mediaWrapper = '*:!w-full *:!h-full overflow-hidden relative';
 export const iconWrapper = 'z-10 size-50 sm:size-70 bg-black-true/70 border-3 rounded-full border-white group-hocus-within:scale-110 transition group-hocus-within:bg-digital-red';
 export const previewImage = 'absolute inset-0 size-full object-cover';
 export const previewIcon = 'text-white size-24 sm:size-30';
-
diff --git a/components/EmbedMedia/EmbedMedia.tsx b/components/EmbedMedia/EmbedMedia.tsx
index 359273d4..427b819b 100644
--- a/components/EmbedMedia/EmbedMedia.tsx
+++ b/components/EmbedMedia/EmbedMedia.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { cnb } from 'cnbuilder';
 import ReactPlayer from 'react-player/lazy';
 import { useEventListener } from 'usehooks-ts';
@@ -11,7 +11,7 @@ import { type PaddingType } from '@/utilities/datasource';
 import { type MediaAspectRatioType, mediaAspectRatios } from '@/utilities/datasource';
 import * as styles from './EmbedMedia.styles';
 
-type EmbedMediaProps = React.HTMLAttributes<HTMLDivElement> & CaptionProps & {
+type EmbedMediaProps = React.HTMLAttributes<HTMLDivElement> & Omit<CaptionProps, 'as'> & {
   mediaUrl: string;
   aspectRatio?: MediaAspectRatioType;
   boundingWidth?: 'site' | 'full';
diff --git a/components/FeaturedStories/FeatureMasonry.styles.ts b/components/FeaturedStories/FeatureMasonry.styles.ts
index 2d1e695f..475b3166 100644
--- a/components/FeaturedStories/FeatureMasonry.styles.ts
+++ b/components/FeaturedStories/FeatureMasonry.styles.ts
@@ -1,14 +1,17 @@
+export const audioWrapper = 'relative lg:col-span-7 bg-black-70 overflow-hidden';
+export const blurOverlay = 'absolute inset-0 size-full backdrop-blur-sm';
+export const audio = 'relative z-10 *:w-4/5 *:mx-auto lg:*:*:aspect-w-2 lg:*:*:aspect-h-1 xl:*:*:aspect-w-4 xl:*:*:aspect-h-1';
 
+export const quoteWrapper = 'relative lg:col-span-5 rs-py-4 px-36 xl:px-58 2xl:px-76 bg-black-70 text-white';
+export const quoteInnerWrapper = 'place-self-center h-fit';
+export const quote = 'relative z-10 !pl-0';
 
+export const imageWrapper = 'lg:col-span-6 size-full';
+export const image = 'size-full object-cover';
 
+export const video = 'lg:col-span-6 bg-black-70';
 
-
-
-
-
-
-
-
+export const caption = 'max-w-prose-wide mt-06em whitespace-pre-line';
 
 
 
diff --git a/components/FeaturedStories/FeatureMasonry.tsx b/components/FeaturedStories/FeatureMasonry.tsx
index bcc8b80c..d4a8217a 100644
--- a/components/FeaturedStories/FeatureMasonry.tsx
+++ b/components/FeaturedStories/FeatureMasonry.tsx
@@ -1,13 +1,12 @@
-import React from 'react';
 import { Container } from '@/components/Container';
 import { Grid } from '@/components/Grid';
 import { FlexBox } from '@/components/FlexBox';
 import { EmbedMedia } from '@/components/EmbedMedia';
 import { ImageOverlay } from '@/components/ImageOverlay';
 import { Quote } from '@/components/Quote';
-import { Text } from '../Typography';
+import { Text } from '@/components/Typography';
 import { getProcessedImage } from '@/utilities/getProcessedImage';
-
+import * as styles from './FeatureMasonry.styles';
 
 type FeatureMasonryProps = React.HTMLAttributes<HTMLDivElement> & {
   audioUrl?: string;
@@ -41,15 +40,15 @@ export const FeatureMasonry = ({
   previewAriaLabel,
   caption,
   isLightCaption,
-  className,
   ...props
 }: FeatureMasonryProps) => {
   return (
-    <Container {...props} width="full">
+    <Container as="figure" width="full" {...props}>
       <Grid lg={12} gap="default">
+        {/* Audio player block */}
         <FlexBox
           alignItems="center"
-          className="relative lg:col-span-7 bg-black-70 overflow-hidden"
+          className={styles.audioWrapper}
         >
           <ImageOverlay
             imageSrc={getProcessedImage(
@@ -59,15 +58,18 @@ export const FeatureMasonry = ({
             )}
             overlay="black-10"
           />
-          <div className="absolute top-0 left-0 size-full backdrop-blur-sm" />
+          <div className={styles.blurOverlay} />
           <EmbedMedia
             mediaUrl={audioUrl}
-            className="relative z-10 rs-py-6 *:w-4/5 *:mx-auto lg:*:*:aspect-w-2 lg:*:*:aspect-h-1 xl:*:*:aspect-w-4 xl:*:*:aspect-h-1"
+            spacingTop={6}
+            spacingBottom={6}
+            className={styles.audio}
           />
         </FlexBox>
+        {/* Quote block */}
         <FlexBox
           justifyContent="center"
-          className="relative lg:col-span-5 rs-py-4 px-36 xl:px-58 2xl:px-76 bg-black-70 text-white"
+          className={styles.quoteWrapper}
         >
           <ImageOverlay
             imageSrc={getProcessedImage(
@@ -77,7 +79,7 @@ export const FeatureMasonry = ({
             )}
             overlay="black-60"
           />
-          <div className="place-self-center h-fit">
+          <div className={styles.quoteInnerWrapper}>
             <Quote
               teaser=" "
               animation="slideUp"
@@ -85,26 +87,34 @@ export const FeatureMasonry = ({
               body={quoteBody}
               isLargeBody
               source={quoteSource}
-              className="relative z-10 !pl-0"
+              className={styles.quote}
             />
           </div>
         </FlexBox>
-        <div className="lg:col-span-6 size-full">
+        {/* Image block */}
+        <div className={styles.imageWrapper}>
           <img
             src={getProcessedImage(imageSrc1, '800x450', imageFocus1)}
             alt={imageAlt1 || ''}
-            className="size-full object-cover"
+            className={styles.image}
           />
         </div>
+        {/* Video block */}
         <EmbedMedia
           mediaUrl={videoUrl}
           isPreview
-          className="lg:col-span-6 bg-black-70"
+          className={styles.video}
           aspectRatio="16x9"
           previewAriaLabel={previewAriaLabel}
         />
       </Grid>
-      <Text variant="caption" color={isLightCaption ? 'white' : 'black-70'} leading="display" className="max-w-prose-wide mt-06em whitespace-pre-line">
+      <Text
+        as="figcaption"
+        variant="caption"
+        color={isLightCaption ? 'white' : 'black-70'}
+        leading="display"
+        className={styles.caption}
+      >
         {caption}
       </Text>
     </Container>
diff --git a/components/Hero/StoryHeroMvp.tsx b/components/Hero/StoryHeroMvp.tsx
index e99d5298..22849e5b 100644
--- a/components/Hero/StoryHeroMvp.tsx
+++ b/components/Hero/StoryHeroMvp.tsx
@@ -82,8 +82,7 @@ export const StoryHeroMvp = ({
   taxonomy = [],
 }: StoryHeroMvpProps) => {
   const useTwoColLayout = isVerticalHero;
-  const hasCaption = hasRichText(caption);
-  const CaptionText = hasCaption ?
+  const CaptionText = hasRichText(caption) ?
     <RichText
       textColor={isDarkCaptionBg ? 'white' : 'black-70'}
       linkColor={isDarkCaptionBg ? 'digital-red-xlight' : 'unset'}
@@ -114,7 +113,6 @@ export const StoryHeroMvp = ({
           videoMp4={mp4Filename}
           videoPosterSrc={posterFilename}
           isLightHero={isLightHero}
-          hasCaption={hasCaption}
           taxonomy={taxonomy}
         />
       ) : (
@@ -147,7 +145,6 @@ export const StoryHeroMvp = ({
           darkOverlay={darkOverlay}
           imageOnLeft={isLeftImage}
           tabColor={paletteAccentColors[tabColorValue]}
-          hasCaption={hasCaption}
           taxonomy={taxonomy}
         />
       )}
@@ -155,7 +152,7 @@ export const StoryHeroMvp = ({
         <CreateBloks blokSection={heroTexturedBar} />
       )}
       {hasRichText(caption) && (
-        <Caption caption={CaptionText} isCaptionInset />
+        <Caption as="div" caption={CaptionText} isCaptionInset />
       )}
     </Container>
   );
diff --git a/components/Hero/StoryHeroStacked.tsx b/components/Hero/StoryHeroStacked.tsx
index 91e28a15..aa5a62cc 100644
--- a/components/Hero/StoryHeroStacked.tsx
+++ b/components/Hero/StoryHeroStacked.tsx
@@ -26,7 +26,6 @@ export type StoryHeroStackedProps = {
   videoWebm?: string;
   videoMp4?: string;
   videoPosterSrc?: string;
-  hasCaption?: boolean;
   isLightHero?: boolean;
   taxonomy?: TaxonomyType[];
 };
@@ -46,7 +45,6 @@ export const StoryHeroStacked = ({
   videoWebm,
   videoMp4,
   videoPosterSrc,
-  hasCaption,
   isLightHero = false,
   taxonomy,
 }: StoryHeroStackedProps) => {
@@ -181,7 +179,6 @@ export const StoryHeroStacked = ({
               <img
                 src={getProcessedImage(imageSrc, '2000x0', imageFocus)}
                 alt={alt || ''}
-                aria-describedby={hasCaption ? 'story-hero-caption' : undefined}
                 fetchPriority="high"
                 width={imageWidth}
                 height={imageHeight}
diff --git a/components/InitiativeCard/InitiativeCard.styles.ts b/components/InitiativeCard/InitiativeCard.styles.ts
index 5b054fd8..94fa75b7 100644
--- a/components/InitiativeCard/InitiativeCard.styles.ts
+++ b/components/InitiativeCard/InitiativeCard.styles.ts
@@ -30,11 +30,11 @@ export const body = (hasTabColor: boolean) => cnb('rs-pl-1 text-current', {
   'border-l-[1.4rem] md:border-l-[2rem]': hasTabColor,
 });
 
-export const cta = (isHorizontal: boolean) => cnb('group/cta inline-block bg-gc-black text-white hocus:text-white stretched-link no-underline hocus:no-underline rs-py-1 rs-pr-1', {
+export const cta = (isHorizontal: boolean) => cnb('group/cta inline-block bg-gc-black text-white hocus:text-white stretched-link no-underline hocus:no-underline rs-py-1 rs-pr-1 hocus-visible:no-underline', {
   'xl:hidden': isHorizontal,
 });
 export const linkText = 'text-19 md:text-25 mr-0 ml-auto last:children:underline';
-export const lastword = 'underline decoration-digital-red-light underline-offset-4 group-hocus/cta:decoration-white';
+export const lastword = 'underline decoration-digital-red-light underline-offset-4 group-hover/cta:decoration-white group-focus-visible/cta:decoration-white';
 export const icon = (hasLinkText?: boolean) => cnb('inline-block mb-0 mt-auto w-20 md:w-24 mr-0',
   hasLinkText? 'ml-10' : 'ml-auto',
 );
diff --git a/components/Media/Caption.tsx b/components/Media/Caption.tsx
index 5178a1f2..e718135d 100644
--- a/components/Media/Caption.tsx
+++ b/components/Media/Caption.tsx
@@ -8,6 +8,7 @@ import * as styles from './MediaWrapper.styles';
  */
 
 export type CaptionProps = React.HTMLAttributes<HTMLDivElement> & {
+  as?: 'figcaption' | 'div';
   caption?: React.ReactNode;
   // Inset the caption to centered container width when the media is full screen width
   isCaptionInset?: boolean;
@@ -15,15 +16,18 @@ export type CaptionProps = React.HTMLAttributes<HTMLDivElement> & {
 };
 
 export const Caption = ({
+  as = 'figcaption',
   caption,
   isCaptionInset,
   captionBgColor = 'transparent',
   className,
   ...props
 }: CaptionProps) => {
+  if (!caption) return null;
+
   return (
     <Container
-      as="figcaption"
+      as={as}
       width={isCaptionInset ? 'site' : 'full'}
       className={cnb(styles.captionWrapper, styles.captionBgColors[captionBgColor])}
       {...props}
diff --git a/components/Media/MediaWrapper.styles.ts b/components/Media/MediaWrapper.styles.ts
index 6d8ae229..3aa98460 100644
--- a/components/Media/MediaWrapper.styles.ts
+++ b/components/Media/MediaWrapper.styles.ts
@@ -20,7 +20,7 @@ export const mediaWrapper = (isFullHeight: boolean, isParallax: boolean) => cnb(
 );
 
 // Caption component styles
-export const captionWrapper = 'mt-0';
+export const captionWrapper = 'mt-0 gc-caption';
 export const caption = (captionBgColor: CaptionBgColorType) => cnb(
   '*:*:leading-display *:*:xl:leading-snug max-w-prose-wide first:*:*:mt-0',
   !!captionBgColor && captionBgColor !== 'transparent' ? 'px-1em py-08em' : 'pt-06em',
diff --git a/components/Media/MediaWrapper.tsx b/components/Media/MediaWrapper.tsx
index e6efebcd..4950a5c6 100644
--- a/components/Media/MediaWrapper.tsx
+++ b/components/Media/MediaWrapper.tsx
@@ -11,7 +11,7 @@ import * as styles from './MediaWrapper.styles';
  * that provides a shared set of layout and caption options.
  */
 
-export type MediaWrapperProps = React.HTMLAttributes<HTMLDivElement> & CaptionProps & {
+export type MediaWrapperProps = React.HTMLAttributes<HTMLDivElement> & Omit<CaptionProps, 'as'> & {
   aspectRatio?: ImageAspectRatioType;
   /**
    * If different aspect ratios at different breakpoints are needed, ie, using the AspectRatio prop is insufficient,
diff --git a/components/Scrollytelling/Scrollytelling.tsx b/components/Scrollytelling/Scrollytelling.tsx
index 41794f98..bc08f2ab 100644
--- a/components/Scrollytelling/Scrollytelling.tsx
+++ b/components/Scrollytelling/Scrollytelling.tsx
@@ -144,7 +144,7 @@ export const Scrollytelling = ({
         </div>
       </Container>
       {caption && (
-        <Caption caption={caption} isCaptionInset />
+        <Caption as="div" caption={caption} isCaptionInset />
       )}
     </Container>
   );
diff --git a/components/Storyblok/SbSection/SbSection.styles.ts b/components/Storyblok/SbSection/SbSection.styles.ts
index 9ecd055a..73d896a2 100644
--- a/components/Storyblok/SbSection/SbSection.styles.ts
+++ b/components/Storyblok/SbSection/SbSection.styles.ts
@@ -35,7 +35,6 @@ export const subhead = (headerAlign?: AlignType) => cnb('relative z-10 rs-mt-3 t
 });
 export const contentWrapper = 'relative z-10';
 export const cta = 'cc md:flex-row *:mx-auto rs-mt-3 gap-20 lg:gap-27 w-fit';
-export const caption = 'gc-caption first:*:mt-0 *:leading-display *:xl:leading-snug pt-06em max-w-prose-wide';
 
 export const bgImage = 'absolute top-0 left-0 size-full object-cover';
 export const overlay = (hasBgGradient?: boolean) => cnb('absolute top-0 left-0 size-full z-10', hasBgGradient ? 'bg-gradient-to-b via-50%' : '');
diff --git a/components/Storyblok/SbSection/SbSection.tsx b/components/Storyblok/SbSection/SbSection.tsx
index 402ded8c..17bd9910 100644
--- a/components/Storyblok/SbSection/SbSection.tsx
+++ b/components/Storyblok/SbSection/SbSection.tsx
@@ -14,9 +14,9 @@ import {
   Text,
   Paragraph,
 } from '@/components/Typography';
+import { Caption } from '@/components/Media/Caption';
 import { Container, type BgColorType } from '@/components/Container';
 import { RichText } from '@/components/RichText';
-import { WidthBox } from '@/components/WidthBox';
 import { accentBgColors, type PaddingType, type MarginType } from '@/utilities/datasource';
 import { paletteAccentColors, type PaletteAccentHexColorType } from '@/utilities/colorPalettePlugin';
 import { type SbImageType, type SbColorStopProps } from '../Storyblok.types';
@@ -136,6 +136,13 @@ export const SbSection = ({
   }
   const animatedBgColor = useTransform(scrollYProgress, stops, bgColors);
 
+  const CaptionRichText = hasRichText(caption) ?
+    <RichText
+      textColor={isLightCaption ? 'white' : 'black-70'}
+      linkColor={isLightCaption ? 'digital-red-xlight' : 'unset'}
+      wysiwyg={caption}
+    /> : undefined;
+
   if (isHidden) {
     return null;
   }
@@ -267,16 +274,7 @@ export const SbSection = ({
           )}
         </Container>
       </m.div>
-      {hasRichText(caption) && (
-        <WidthBox boundingWidth="site" width={captionColumnWidth} bgColor={captionBgColor}>
-          <RichText
-            wysiwyg={caption}
-            textColor={isLightCaption ? 'white' : 'black-70'}
-            linkColor={isLightCaption ? 'digital-red-xlight' : 'unset'}
-            className={styles.caption}
-          />
-        </WidthBox>
-      )}
+      <Caption as="div" caption={CaptionRichText} isCaptionInset />
     </Container>
   );
 };


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This PR implements accessibility improvements by updating caption rendering and removing obsolete aria-describedby attributes while also adjusting CTA underline behavior on initiative cards.  
- Updated caption components to support dynamic element types (div vs. figcaption) for various contexts  
- Removed aria-describedby from hero and poster images and refined CTA styling for improved focus/hover behavior

### Reviewed Changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 1 comment.

<details>
<summary>Show a summary per file</summary>

| File                                                              | Description                                                         |
|-------------------------------------------------------------------|---------------------------------------------------------------------|
| components/Storyblok/SbSection/SbSection.tsx                      | Converted caption rendering from a WidthBox to the new Caption API. |
| components/Scrollytelling/Scrollytelling.tsx                      | Updated Caption usage to explicitly render as a div.                |
| components/Media/Caption.tsx                                        | Enhanced to accept an "as" prop, defaulting to "figcaption".          |
| components/Media/MediaWrapper.tsx                                   | Updated prop types by omitting the "as" property for caption props.   |
| components/InitiativeCard/InitiativeCard.styles.ts                  | Modified CTA styles to adjust underline behavior on hover/focus.      |
| components/Hero/StoryHeroStacked.tsx & StoryHeroMvp.tsx             | Removed aria-describedby attribute on media, per a11y updates.        |
| components/FeaturedStories/FeatureMasonry.tsx                       | Revised figure/figcaption structure for proper semantic markup.       |
| components/BlurryPoster/BlurryPoster.tsx                            | Removed aria-describedby from images to align with accessibility fixes.|
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**components/InitiativeCard/InitiativeCard.styles.ts:33**
* [nitpick] Ensure that the new pseudo-class names (group-hover/cta and group-focus-visible/cta) are supported by your PostCSS or CSS-in-JS setup and remain consistent with the rest of the codebase's naming conventions.
```
export const cta = (isHorizontal: boolean) => cnb('group/cta inline-block bg-gc-black text-white hocus:text-white stretched-link no-underline hocus:no-underline rs-py-1 rs-pr-1 hocus-visible:no-underline', {
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-07T21:06:53Z
user: login: yvonnetangsu
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-07T21:08:53Z
user: login: sherakama
body: Looks good. 

I also rand the page through the HTML validator to ensure it was all good and it did not pick up anything related to the caption. 

It does look like we have some other HTML validation bugs with links but nothing too major.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-07T21:37:38Z

Review comments:
diff_hunk: @@ -11,7 +11,7 @@ import * as styles from './MediaWrapper.styles';
  * that provides a shared set of layout and caption options.
  */
 
path: components/Media/MediaWrapper.tsx
user: login: Copilot
body: [nitpick] Consider adding a code comment to document why the 'as' property is omitted in MediaWrapperProps for clarity and future maintainability.
```suggestion

/**
 * The 'as' property is omitted from CaptionProps because MediaWrapper does not support
 * changing the rendered element type for captions. This ensures consistent behavior
 * and simplifies the component's API.
 */
```
created_at: 2025-05-07T21:06:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -267,16 +274,7 @@ export const SbSection = ({
           )}
         </Container>
       </m.div>
-      {hasRichText(caption) && (
-        <WidthBox boundingWidth="site" width={captionColumnWidth} bgColor={captionBgColor}>
-          <RichText
-            wysiwyg={caption}
-            textColor={isLightCaption ? 'white' : 'black-70'}
-            linkColor={isLightCaption ? 'digital-red-xlight' : 'unset'}
-            className={styles.caption}
-          />
-        </WidthBox>
-      )}
+      <Caption as="div" caption={CaptionRichText} isCaptionInset />
path: components/Storyblok/SbSection/SbSection.tsx
user: login: yvonnetangsu
body: Just using the Caption component previously created for the Section caption
created_at: 2025-05-07T21:08:53Z
author_association: MEMBER

PR comments:
user: login: netlify[bot]
created_at: 2025-04-30T18:58:03Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *giving-campaign* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 86bb61d32558bacad58c0c20d76eca821afd923d |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/giving-campaign/deploys/681bc5a6ca2ed900087182d1 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-414--giving-campaign.netlify.app](https://deploy-preview-414--giving-campaign.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTQxNC0tZ2l2aW5nLWNhbXBhaWduLm5ldGxpZnkuYXBwIn0.eXF_d-Ebpqyn0DIyHyln9nU_aVhGzydT9HCT3AJGhng)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [giving-campaign Preview](https://deploy-preview-414--giving-campaign.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/giving-campaign/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: sherakama
created_at: 2025-05-07T21:35:11Z
body: ![Screenshot 2025-05-07 at 14 34 39](https://github.com/user-attachments/assets/52bcbcc0-2c35-41c7-bb77-51308fb2cfdc)
![Screenshot 2025-05-07 at 14 33 30](https://github.com/user-attachments/assets/18e40892-c0a3-42b9-a309-3c7d53cc925c)
![Screenshot 2025-05-07 at 14 33 06](https://github.com/user-attachments/assets/ac85722d-68e9-4607-8d78-0b27223067cc)
![Screenshot 2025-05-07 at 14 32 33](https://github.com/user-attachments/assets/f90c8072-cf3e-4e57-bc05-1506cfed5bcc)
![Screenshot 2025-05-07 at 14 31 36](https://github.com/user-attachments/assets/eba57238-5511-4a9f-a368-39c9a62df546)
![Screenshot 2025-05-07 at 14 31 09](https://github.com/user-attachments/assets/cdf56e22-858b-4db8-bce5-e853eb3170a8)

author_association: MEMBER
user: login: yvonnetangsu
created_at: 2025-05-07T21:40:01Z
body: > Looks good.
> 
> I also rand the page through the HTML validator to ensure it was all good and it did not pick up anything related to the caption.
> 
> It does look like we have some other HTML validation bugs with links but nothing too major.

Thank you for testing @sherakama! I'll run a validator later - thank you for letting me know.
author_association: MEMBER