diff --git a/about/callout-media-banner-showcase.page.tsx b/about/callout-media-banner-showcase.page.tsx
index 70177bba43..5ca868aeba 100644
--- a/about/callout-media-banner-showcase.page.tsx
+++ b/about/callout-media-banner-showcase.page.tsx
@@ -14,8 +14,11 @@ export default function CalloutMediaBannerShowcase() {
console.log(`CalloutMediaBanner button clicked: ${message}`);
};
- // Sample background image (placeholder)
- const sampleBackgroundImage = "data:image/svg+xml,%3Csvg width='1280' height='400' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%2321E46B;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%230DAA3E;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1280' height='400' fill='url(%23grad)' /%3E%3C/svg%3E";
+ // Sample background images (placeholders)
+ // To load an image from the `public` folder in Next.js (or Create React App), use the path relative to the `public` directory, starting with a slash.
+ // For example, if you have `/public/backgrounds/Callout.jpg`, use:
+ const sampleBackgroundImage = "/img/backgrounds/Callout.jpg";
+ const sampleLightBackgroundImage = "data:image/svg+xml,%3Csvg width='1280' height='400' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient id='grad' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' style='stop-color:%2370EE97;stop-opacity:1' /%3E%3Cstop offset='100%25' style='stop-color:%23C0A7FF;stop-opacity:1' /%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='1280' height='400' fill='url(%23grad)' /%3E%3C/svg%3E";
return (
@@ -218,6 +221,7 @@ export default function CalloutMediaBannerShowcase() {
When backgroundImage is provided, it overrides the variant prop.
The component automatically adds a gradient overlay to ensure text remains readable.
+ You can also specify textColor to fix the text color across both light and dark modes.
@@ -226,26 +230,63 @@ export default function CalloutMediaBannerShowcase() {
If backgroundImage is provided: Image is used, variant color is ignored
If only variant is provided: Solid color background is applied
If neither: Defaults to white background (default variant)
- Text color: Always white when using background image for readability
+ Text color: Defaults to white, or set to black via textColor="black"
+ Fixed text color: Text color remains consistent across light and dark modes
+ Overlay gradient: Dark overlay for white text, light overlay for black text
-
handleClick('image-primary') }}
- tertiaryButton={{ label: "Explore Features", onClick: () => handleClick('image-tertiary') }}
- />
+ {/* White Text Example */}
+
+
+
+
+
+ White Text (Default) - textColor="white"
+
+ Best for dark or colorful images. Includes dark overlay gradient.
+
+
+
+
+
handleClick('image-white-primary') }}
+ />
+
+
+ {/* Black Text Example */}
+
+
+
+
+
+ Black Text - textColor="black"
+
+ Best for light or bright images. Includes light overlay gradient. Text remains black in both light and dark modes.
+
+
+
+
+
handleClick('image-black-primary') }}
+ tertiaryButton={{ label: "Explore Features", onClick: () => handleClick('image-black-tertiary') }}
+ />
+
- Note: The image variant includes an automatic gradient overlay (dark to light) to maintain text legibility.
+ Note: The image variant includes an automatic gradient overlay. White text gets a dark overlay, black text gets a light overlay. Text colors remain fixed across both light and dark modes.
@@ -499,6 +540,14 @@ export default function CalloutMediaBannerShowcase() {
Background image URL - overrides variant color
+ {/* textColor */}
+
heading
diff --git a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss
index 4ee8bb8aae..c43f0ebc45 100644
--- a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss
+++ b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.scss
@@ -8,7 +8,8 @@
// .bds-callout-media-banner--lilac - Lilac variant
// .bds-callout-media-banner--green - Green variant
// .bds-callout-media-banner--gray - Gray variant
-// .bds-callout-media-banner--image - Background image variant
+// .bds-callout-media-banner--image - Background image variant (white text by default)
+// .bds-callout-media-banner--image-text-black - Image variant with black text (fixed across light/dark)
// .bds-callout-media-banner--centered - Centered content modifier (no buttons or no heading)
// .bds-callout-media-banner__content - Content wrapper
// .bds-callout-media-banner__text - Text content container
@@ -83,7 +84,7 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3);
}
// Desktop breakpoint
- @include media-breakpoint-up(xl) {
+ @include media-breakpoint-up(lg) {
padding: $bds-cmb-padding-desktop;
min-height: 360px;
}
@@ -152,7 +153,7 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3);
// Typography - Heading MD token (Tobias Light)
@include type(heading-md);
margin: 0;
- color: inherit;
+ color: inherit !important;
}
.bds-callout-media-banner__subheading {
@@ -164,14 +165,20 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3);
// =============================================================================
// Action Buttons
-// =============================================================================
+// ==================================================================== c=========
.bds-callout-media-banner__actions {
display: flex;
- flex-direction: row;
- align-items: center;
- gap: $bds-cmb-button-gap;
+ flex-direction: column;
+ align-items: flex-start;
flex-wrap: wrap;
+ gap: 8px;
+ // Tablet breakpoint
+ @include media-breakpoint-up(md) {
+ flex-direction: row;
+ align-items: center;
+ gap: 0px;
+ }
}
// =============================================================================
@@ -212,7 +219,7 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3);
// No background-color (image takes precedence)
background-color: transparent;
- // Add overlay gradient for text readability
+ // Add overlay gradient for text readability (default: dark overlay for white text)
&::before {
content: '';
position: absolute;
@@ -229,10 +236,24 @@ $bds-cmb-image-overlay: rgba(0, 0, 0, 0.3);
z-index: 0;
pointer-events: none;
}
+}
+
+// Image variant with black text modifier
+// Fixed black text across both light and dark modes
+.bds-callout-media-banner--image-text-black {
+ // Light overlay for black text readability
+ &::before {
+ background: linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.4) 0%,
+ rgba(255, 255, 255, 0.2) 50%,
+ rgba(255, 255, 255, 0.1) 100%
+ );
+ }
- // Ensure text is readable over image
+ // Force black text in both light and dark modes
.bds-callout-media-banner__text {
- color: $white;
+ color: $black !important;
}
}
@@ -286,12 +307,19 @@ html.dark {
}
}
- // Image variant remains the same (overlay already provides contrast)
+ // Image variant with white text remains white
.bds-callout-media-banner--image {
.bds-callout-media-banner__text {
color: $white;
}
}
+
+ // Image variant with black text remains black (fixed across modes)
+ .bds-callout-media-banner--image-text-black {
+ .bds-callout-media-banner__text {
+ color: $black !important;
+ }
+ }
}
// =============================================================================
@@ -344,11 +372,18 @@ html.light {
}
}
- // Image variant
+ // Image variant with white text remains white
.bds-callout-media-banner--image {
.bds-callout-media-banner__text {
color: $white;
}
}
+
+ // Image variant with black text remains black (fixed across modes)
+ .bds-callout-media-banner--image-text-black {
+ .bds-callout-media-banner__text {
+ color: $black !important;
+ }
+ }
}
diff --git a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx
index cdfb3c5b76..0f0de9b41d 100644
--- a/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx
+++ b/shared/patterns/CalloutMediaBanner/CalloutMediaBanner.tsx
@@ -8,6 +8,8 @@ export interface CalloutMediaBannerProps {
variant?: 'default' | 'light-gray' | 'lilac' | 'green' | 'gray';
/** Background image URL - overrides variant color when provided */
backgroundImage?: string;
+ /** Text color for image variant - fixes text color across light/dark modes (only applicable when backgroundImage is provided) */
+ textColor?: 'white' | 'black';
/** Main heading text */
heading?: string;
/** Subheading/description text */
@@ -45,17 +47,28 @@ export interface CalloutMediaBannerProps {
* />
*
* @example
- * // With background image
+ * // With background image (white text - default)
*
+ *
+ * @example
+ * // With background image and black text (fixed across light/dark modes)
+ *
*/
export const CalloutMediaBanner: React.FC
= ({
variant = 'default',
backgroundImage,
+ textColor = 'white',
heading,
subheading,
primaryButton,
@@ -79,6 +92,8 @@ export const CalloutMediaBanner: React.FC = ({
!backgroundImage && `bds-callout-media-banner--${variant}`,
// Add image class when backgroundImage is provided
backgroundImage && 'bds-callout-media-banner--image',
+ // Add text color modifier for image variant
+ backgroundImage && textColor === 'black' && 'bds-callout-media-banner--image-text-black',
// Add centered class when content should be centered
shouldCenter && 'bds-callout-media-banner--centered',
className
diff --git a/shared/patterns/CalloutMediaBanner/README.md b/shared/patterns/CalloutMediaBanner/README.md
index a83354b3cb..b92157e659 100644
--- a/shared/patterns/CalloutMediaBanner/README.md
+++ b/shared/patterns/CalloutMediaBanner/README.md
@@ -34,7 +34,8 @@ The component automatically adapts its spacing and typography based on viewport
| `lilac` | Lilac 300 (#C0A7FF) | Black (#141414) | Black |
| `green` | Green 200 (#70EE97) | Black (#141414) | Black |
| `gray` | Gray 300 (#CAD4DF) | Black (#141414) | Black |
-| `image` | Background Image | White (#FFFFFF) | Green |
+| `image` (textColor='white') | Background Image | White (#FFFFFF) | Green |
+| `image` (textColor='black') | Background Image | Black (#141414) | Green |
### Dark Mode
@@ -45,7 +46,8 @@ The component automatically adapts its spacing and typography based on viewport
| `lilac` | Lilac 400 (#7649E3) | White (#FFFFFF) | Black |
| `green` | Green 300 (#21E46B) | Black (#141414) | Black |
| `gray` | Gray 600 (#454549) | White (#FFFFFF) | Black |
-| `image` | Background Image | White (#FFFFFF) | Green |
+| `image` (textColor='white') | Background Image | White (#FFFFFF) | Green |
+| `image` (textColor='black') | Background Image | Black (#141414) | Green |
## Props API
@@ -55,6 +57,8 @@ interface CalloutMediaBannerProps {
variant?: 'default' | 'light-gray' | 'lilac' | 'green' | 'gray';
/** Background image URL - overrides variant color when provided */
backgroundImage?: string;
+ /** Text color for image variant - fixes text color across light/dark modes (only applicable when backgroundImage is provided) */
+ textColor?: 'white' | 'black';
/** Main heading text */
heading?: string;
/** Subheading/description text */
@@ -80,6 +84,7 @@ interface CalloutMediaBannerProps {
- `variant`: `'default'`
- `backgroundImage`: `undefined`
+- `textColor`: `'white'` (only used when `backgroundImage` is provided)
- `primaryButton`: `undefined`
- `tertiaryButton`: `undefined`
- `className`: `''`
@@ -100,7 +105,7 @@ import { CalloutMediaBanner } from 'shared/patterns/CalloutMediaBanner';
/>
```
-### With Background Image
+### With Background Image (White Text - Default)
```tsx
```
+### With Background Image (Black Text)
+
+```tsx
+
+```
+
### Single Button
```tsx
@@ -154,8 +171,9 @@ import { CalloutMediaBanner } from 'shared/patterns/CalloutMediaBanner';
When `backgroundImage` is provided, it **overrides** the `variant` prop:
- ✅ Image is used as background
- ✅ No solid color background is applied
-- ✅ Text color is always white for readability
-- ✅ Gradient overlay is automatically added
+- ✅ Text color defaults to white (can be set to black via `textColor` prop)
+- ✅ Text color remains fixed across both light and dark modes
+- ✅ Gradient overlay is automatically added (dark overlay for white text, light overlay for black text)
### Button Color Logic
@@ -194,19 +212,20 @@ The component **already wraps content in PageGrid structure**. Do not nest it in
### BEM Class Structure
```scss
-.bds-callout-media-banner // Base banner
-.bds-callout-media-banner--default // White background variant
-.bds-callout-media-banner--light-gray // Light gray variant
-.bds-callout-media-banner--lilac // Lilac variant
-.bds-callout-media-banner--green // Green variant
-.bds-callout-media-banner--gray // Gray variant
-.bds-callout-media-banner--image // Background image variant
-.bds-callout-media-banner--no-actions // No buttons modifier
-.bds-callout-media-banner__content // Content wrapper
-.bds-callout-media-banner__text // Text container
-.bds-callout-media-banner__heading // Heading element
-.bds-callout-media-banner__subheading // Subheading element
-.bds-callout-media-banner__actions // Button container
+.bds-callout-media-banner // Base banner
+.bds-callout-media-banner--default // White background variant
+.bds-callout-media-banner--light-gray // Light gray variant
+.bds-callout-media-banner--lilac // Lilac variant
+.bds-callout-media-banner--green // Green variant
+.bds-callout-media-banner--gray // Gray variant
+.bds-callout-media-banner--image // Background image variant (white text)
+.bds-callout-media-banner--image-text-black // Background image with black text (fixed across modes)
+.bds-callout-media-banner--centered // Centered content modifier
+.bds-callout-media-banner__content // Content wrapper
+.bds-callout-media-banner__text // Text container
+.bds-callout-media-banner__heading // Heading element
+.bds-callout-media-banner__subheading // Subheading element
+.bds-callout-media-banner__actions // Button container
```
### Typography Tokens
@@ -271,8 +290,12 @@ $gray-600 // Gray (dark)
- Minimum recommended resolution: 1920x600px for sharp display
- Use images with clear focal points on the left/center
-- Ensure sufficient contrast for white text readability
-- Test with the automatic gradient overlay
+- **Text Color Selection**:
+ - Use `textColor="white"` (default) for dark or colorful images
+ - Use `textColor="black"` for light or bright images
+ - Text color remains fixed across both light and dark modes
+- Ensure sufficient contrast between image and text
+- Test with the automatic gradient overlay (dark for white text, light for black text)
## Files
diff --git a/static/css/devportal2024-v1.css b/static/css/devportal2024-v1.css
index 4a8b36f9c3..d0fd8fea50 100644
--- a/static/css/devportal2024-v1.css
+++ b/static/css/devportal2024-v1.css
@@ -16903,7 +16903,7 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle {
padding: 24px;
}
}
-@media (min-width: 1280px) {
+@media (min-width: 992px) {
.bds-callout-media-banner {
padding: 40px 32px;
min-height: 360px;
@@ -16978,7 +16978,7 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle {
}
.bds-callout-media-banner__heading {
margin: 0;
- color: inherit;
+ color: inherit !important;
}
.bds-callout-media-banner__subheading {
@@ -17012,10 +17012,17 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle {
.bds-callout-media-banner__actions {
display: flex;
- flex-direction: row;
- align-items: center;
- gap: 24px;
+ flex-direction: column;
+ align-items: flex-start;
flex-wrap: wrap;
+ gap: 8px;
+}
+@media (min-width: 576px) {
+ .bds-callout-media-banner__actions {
+ flex-direction: row;
+ align-items: center;
+ gap: 0px;
+ }
}
.bds-callout-media-banner--default {
@@ -17052,8 +17059,12 @@ html.light .bds-hero-split-media--accent .bds-hero-split-media__subtitle {
z-index: 0;
pointer-events: none;
}
-.bds-callout-media-banner--image .bds-callout-media-banner__text {
- color: #FFFFFF;
+
+.bds-callout-media-banner--image-text-black::before {
+ background: linear-gradient(135deg, rgba(255, 255, 255, 0.4) 0%, rgba(255, 255, 255, 0.2) 50%, rgba(255, 255, 255, 0.1) 100%);
+}
+.bds-callout-media-banner--image-text-black .bds-callout-media-banner__text {
+ color: #141414 !important;
}
html.dark .bds-callout-media-banner--default {
@@ -17089,6 +17100,9 @@ html.dark .bds-callout-media-banner--gray .bds-callout-media-banner__text {
html.dark .bds-callout-media-banner--image .bds-callout-media-banner__text {
color: #FFFFFF;
}
+html.dark .bds-callout-media-banner--image-text-black .bds-callout-media-banner__text {
+ color: #141414 !important;
+}
html.light .bds-callout-media-banner--default {
background-color: #FFFFFF;
@@ -17123,6 +17137,9 @@ html.light .bds-callout-media-banner--gray .bds-callout-media-banner__text {
html.light .bds-callout-media-banner--image .bds-callout-media-banner__text {
color: #FFFFFF;
}
+html.light .bds-callout-media-banner--image-text-black .bds-callout-media-banner__text {
+ color: #141414 !important;
+}
pre {
color: #FFFFFF;
diff --git a/static/img/backgrounds/Callout.jpg b/static/img/backgrounds/Callout.jpg
new file mode 100644
index 0000000000..9649c492a0
Binary files /dev/null and b/static/img/backgrounds/Callout.jpg differ