Skip to content

Conversation

@dmnktoe
Copy link
Owner

@dmnktoe dmnktoe commented Jan 20, 2026

Summary by CodeRabbit

  • New Features

    • Tablet/desktop-responsive spacing controls added across containers, sections, grids and media blocks
    • New typography controls (font size, line height, weight, opacity, letter spacing)
    • Expanded responsive grid with finer column positioning and auto-fit behavior
    • Image auto-centering and richer spacing options, including negative spacing
  • Improvements

    • More fluid, breakpoint-driven layout behavior and improved list styling defaults (disc/decimal)

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 20, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
httpjpg Ready Ready Preview, Comment Jan 20, 2026 10:42pm
httpjpg-docs Ready Ready Preview, Comment Jan 20, 2026 10:42pm
httpjpg-storybook Ready Ready Preview, Comment Jan 20, 2026 10:42pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 20, 2026

Walkthrough

Adds comprehensive responsive spacing, margin, typography, and grid positioning support (base/md/lg) across Storyblok UI components; removes SbMediaWrapper and replaces it with inline responsive spacing logic; updates Storyblok schemas, datasources, Panda config, and Storybook magazine layout to use responsive gridColumn rules.

Changes

Cohort / File(s) Summary
Schema & Datasources
packages/storyblok-sync/scripts/sync-components.ts, packages/storyblok-sync/scripts/sync-datasources.ts
Expanded Storyblok component schemas with tablet/desktop spacing and typography fields; datasources now include negative spacing entries and grid columns 1–12.
Spacing Utilities & Panda config
packages/storyblok-ui/src/lib/spacing-utils.ts, packages/ui/panda.config.ts
mapSpacingToToken now supports negative values and returns undefined for falsy inputs; moved/expanded responsive gridTemplateColumns and grid positioning rules into responsive static CSS.
Core Grid / Layout
packages/storyblok-ui/src/components/grid/SbGrid.tsx, packages/storyblok-ui/src/components/grid-item/SbGridItem.tsx, packages/ui/src/components/grid/grid-item.tsx
Added ml/mr props; introduced breakpoint-specific colStart/colEnd (Md/Lg); replaced inline-style custom props with responsive CSS/class generation and conditional inline styles.
Container & Section
packages/storyblok-ui/src/components/container/SbContainer.tsx, packages/storyblok-ui/src/components/section/SbSection.tsx
Added responsive marginLeft/marginRight and many Md/Lg padding/margin props; implemented filterUndefined and constructed responsive spacing objects applied via css.
Media (Image/Video/Slideshow) & Media Wrapper removal
packages/storyblok-ui/src/components/image/SbImage.tsx, packages/storyblok-ui/src/components/video/SbVideo.tsx, packages/storyblok-ui/src/components/slideshow/SbSlideshow.tsx, packages/storyblok-ui/src/components/media-wrapper/*, packages/storyblok-ui/src/index.ts
Removed SbMediaWrapper and its exports; replaced with div-based wrappers and tokenized responsive spacing props (pt/pb/mt/mb/ml/mr + Md/Lg variants) and width handling across Image/Video/Slideshow.
Typography (Headline / Paragraph / Richtext)
packages/storyblok-ui/src/components/headline/SbHeadline.tsx, packages/storyblok-ui/src/components/paragraph/SbParagraph.tsx, packages/storyblok-richtext/src/richtext.tsx, packages/ui/src/components/paragraph/paragraph.tsx
Added fontSize/lineHeight/fontWeight/opacity/letterSpacing props; mapColor/mapFontWeight usage; Paragraph now respects custom css color override; Richtext forwards textAlign and adds inter-paragraph spacing.
Storybook / Magazine Layout
apps/storybook/stories/Grid.stories.tsx
Converted magazine story to fully responsive gridColumn definitions across breakpoints; updated padding to responsive values and added overflowX: hidden.
List components
packages/ui/src/components/list/*
Set list display/listStyle defaults and refined paragraph spacing within list items.
Small API surface removals
packages/storyblok-ui/src/components/media-wrapper/index.ts, packages/storyblok-ui/src/index.ts
Removed re-exports and public exports for SbMediaWrapper and SbMediaWrapperProps.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 I hopped through breakpoints, soft and spry,

grids stretched wide beneath the sky.
Wrappers gone — divs take their place,
spacing tokens set the pace.
Hooray! Responsive fields dance nimbly — hi! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: grid and richtext improvements' accurately reflects the core changes across multiple components, including grid enhancements, responsive spacing refinements, and richtext handling updates.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@packages/storyblok-sync/scripts/sync-components.ts`:
- Around line 954-968: SbParagraph's Storyblok schema is missing the new
fontSize field, so editors can't set it; add a fontSize entry alongside the
existing lineHeight/opacity/letterSpacing fields in the paragraph component
schema (the same object that currently contains lineHeight, opacity,
letterSpacing) — create a field named fontSize with a display_name like "Font
Size" and a suitable type and description (mirror the Headline fontSize field's
shape) so the SbParagraph component exposes the prop to editors.

In `@packages/storyblok-ui/src/components/container/SbContainer.tsx`:
- Around line 133-139: The responsive spacing construction uses the || operator
which treats the string "0" as falsy and incorrectly falls back to base values;
update each responsive spacing block (responsivePt, responsivePx, responsivePy,
responsivePb, responsiveMt, responsiveMb, responsiveMy, responsiveMl,
responsiveMr) to use the nullish coalescing operator (??) when combining
mapSpacingToToken(...) results with fallbacks so that valid "0" tokens are
preserved (e.g., replace occurrences of "mapSpacingToToken(...) || ptValue" with
"mapSpacingToToken(...) ?? ptValue" in SbContainer.tsx).

In `@packages/storyblok-ui/src/components/grid-item/SbGridItem.tsx`:
- Around line 144-187: The responsive handling drops rowStart/rowEnd because
buildGridRow only returns a span and responsiveGridRow only uses rowSpan; update
row handling so responsiveGridRow preserves start/end values: create/modify a
function (e.g., buildGridRow or buildGridRowValue) that accepts rowStart,
rowSpan, rowEnd (use parsePosition for start/end and parseRowSpan for span) and
returns either "start / end" when start/end exist or "span N" when only span
exists; then change responsiveGridRow to call that function for base/md/lg (pass
rowStart/rowSpan/rowEnd, rowStartMd/rowSpanMd/rowEndMd,
rowStartLg/rowSpanLg/rowEndLg) and keep responsiveCss/GridItem usage the same so
rowStart/rowEnd are preserved in responsive mode.

In `@packages/storyblok-ui/src/components/headline/SbHeadline.tsx`:
- Around line 56-63: In SbHeadline, move typography-related inline styles out of
the style prop and into the component's css prop so Panda can resolve tokens:
remove fontSize, lineHeight, fontWeight, letterSpacing, opacity (and
mappedColor/color token usage) from the style object and add them to the
existing css prop merge alongside spacing props; ensure you preserve any
computed/mapped values (e.g., mappedColor) when assigning to css and leave style
only for non-token, imperative values (or remove it entirely if unused).

In `@packages/storyblok-ui/src/components/image/SbImage.tsx`:
- Around line 301-344: The caption in SbImage is currently rendered outside the
spacing wrapper so spacing props don't affect the gap between image and caption;
update the JSX in SbImage to move the SbCaption (rendered with <SbCaption
data={caption} />) inside the inner spacing wrapper that contains the Image (the
div with px/py/pl/pr/... responsive styles and the child div using
RESPONSIVE_WIDTH_CLASSES), mirroring SbVideo's structure so the spacing props
apply to both image and caption.

In `@packages/ui/src/components/grid/grid-item.tsx`:
- Around line 86-100: The current shared flag hasGridClassInClassName causes
row-only classes to falsely mark hasResponsiveGridColumn and drop column
defaults; split the detection into two flags (e.g.,
hasGridColumnClassInClassName and hasGridRowClassInClassName) that check
className for column-specific tokens ("grid-c_", "md:grid-c_", "lg:grid-c_") vs
row-specific tokens ("grid-r_", "md:grid-r_", "lg:grid-r_"), then use the column
flag with hasResponsiveGridColumn (alongside cssProp?.gridColumn) and the row
flag with hasResponsiveGridRow (alongside cssProp?.gridRow) so colSpan/colStart
defaults are preserved when only row classes exist.

In `@packages/ui/src/components/list/list-item.tsx`:
- Around line 42-43: The base style's fontSize: "inherit" is never used because
the component's size prop defaults to "sm" via defaultVariants and the prop
default; update the component (list-item.tsx) to either add an explicit
"inherit" option to the size variants (e.g., include a "inherit" variant that
leaves fontSize as "inherit" and add "inherit" to the Size prop type) or make
size optional/remove the defaultVariants size so inheritance is possible by
default; also update the prop typing and any places that rely on defaultVariants
(and tests/usage) to reflect the new optional/default behavior.

In `@packages/ui/src/components/paragraph/paragraph.tsx`:
- Around line 211-223: The paragraph component currently sets colorVariant to
undefined when a custom color is provided, but Panda treats undefined as "not
provided" and applies the recipe defaultVariants; change the logic that computes
colorVariant (used in paragraphRecipe.raw call) to use null instead of undefined
when hasCustomColor is true so the color variant is explicitly removed and the
custom css prop color isn't overridden by defaultVariants (refer to
hasCustomColor, colorVariant, and paragraphRecipe.raw in the cx/css invocation).
🧹 Nitpick comments (5)
packages/ui/src/components/list/list-item.tsx (1)

46-48: Selector refinement looks good; consider avoiding !important.

Using & > p to target only direct child paragraphs is more precise and prevents unintended side effects on deeply nested content.

However, !important is generally a code smell. If this is necessary to override specificity from paragraph components, consider documenting why, or using a higher-specificity selector if feasible.

packages/storyblok-ui/src/components/section/SbSection.tsx (1)

94-100: Consider extracting filterUndefined to a shared util.

This helper is now repeated across components (e.g., Section/Container). Centralizing it reduces drift and keeps responsive spacing logic consistent.

packages/storyblok-ui/src/components/slideshow/SbSlideshow.tsx (1)

104-164: Consider extracting the responsive spacing builder into a shared utility.

This responsive spacing logic is duplicated across SbSlideshow, SbVideo, and SbImage. A shared utility function would reduce duplication and ensure consistent behavior.

♻️ Suggested utility function

In packages/storyblok-ui/src/lib/spacing-utils.ts:

export function buildResponsiveSpacing(
  base: string | undefined,
  md: string | undefined,
  lg: string | undefined
): string | { base: string | undefined; md: string | undefined; lg: string | undefined } | undefined {
  const baseValue = mapSpacingToToken(base);
  const mdValue = mapSpacingToToken(md);
  const lgValue = mapSpacingToToken(lg);
  
  if (md || lg) {
    return {
      base: baseValue,
      md: mdValue || baseValue,
      lg: lgValue || mdValue || baseValue,
    };
  }
  return baseValue;
}

Then usage becomes:

const responsivePt = buildResponsiveSpacing(pt, ptMd, ptLg);
const responsivePb = buildResponsiveSpacing(pb, pbMd, pbLg);
// ...
apps/storybook/stories/Grid.stories.tsx (1)

527-534: Minor: Redundant position declaration.

The position: { base: "relative", lg: "relative" } is equivalent to just position: "relative" since the value doesn't change across breakpoints.

♻️ Simplify position
          <Box
            css={{
-             position: { base: "relative", lg: "relative" },
+             position: "relative",
              left: { base: "0", lg: "50%" },
              right: { base: "0", lg: "50%" },
              marginLeft: { base: "0", lg: "-50vw" },
              marginRight: { base: "0", lg: "-50vw" },
              width: { base: "100%", lg: "100vw" },
            }}
          >
packages/storyblok-ui/src/components/video/SbVideo.tsx (1)

141-217: Same duplication as noted in SbSlideshow.

The responsive spacing logic (lines 141-201) is identical to SbSlideshow. Consider the shared utility function suggested earlier.

The spacingStyles object (lines 210-217) is a good pattern for reusing the computed values across the consent placeholder and main video render paths.

Comment on lines +954 to +968
lineHeight: {
type: "text",
display_name: "Line Height",
description: "Custom line height (e.g., '1.5', '2')",
},
opacity: {
type: "text",
display_name: "Opacity",
description: "Opacity value (0-1, e.g., '0.7')",
},
letterSpacing: {
type: "text",
display_name: "Letter Spacing",
description: "Letter spacing (e.g., '0.15em', '2px')",
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Paragraph schema misses new fontSize field.

SbParagraph now accepts a fontSize prop, but the Storyblok schema doesn’t expose it, so editors can’t use it. Consider adding a fontSize field similar to Headline.

✅ Suggested schema addition
       align: {
         type: "option",
         display_name: "Text Align",
         options: [
           { name: "Left", value: "left" },
           { name: "Center", value: "center" },
           { name: "Right", value: "right" },
         ],
       },
+      fontSize: {
+        type: "text",
+        display_name: "Font Size",
+        description: "Custom font size (e.g., '2rem', '24px')",
+      },
       lineHeight: {
         type: "text",
         display_name: "Line Height",
         description: "Custom line height (e.g., '1.5', '2')",
       },
🤖 Prompt for AI Agents
In `@packages/storyblok-sync/scripts/sync-components.ts` around lines 954 - 968,
SbParagraph's Storyblok schema is missing the new fontSize field, so editors
can't set it; add a fontSize entry alongside the existing
lineHeight/opacity/letterSpacing fields in the paragraph component schema (the
same object that currently contains lineHeight, opacity, letterSpacing) — create
a field named fontSize with a display_name like "Font Size" and a suitable type
and description (mirror the Headline fontSize field's shape) so the SbParagraph
component exposes the prop to editors.

Comment on lines +133 to +139
const responsivePt =
ptMd || ptLg
? filterUndefined({
base: ptValue,
md: mapSpacingToToken(ptMd) || ptValue,
lg: mapSpacingToToken(ptLg) || mapSpacingToToken(ptMd) || ptValue,
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's check the file size and read the SbContainer.tsx file
wc -l packages/storyblok-ui/src/components/container/SbContainer.tsx

Repository: dmnktoe/httpjpg

Length of output: 125


🏁 Script executed:

# Read the file to see the full context
cat -n packages/storyblok-ui/src/components/container/SbContainer.tsx

Repository: dmnktoe/httpjpg

Length of output: 6788


🏁 Script executed:

# Find the mapSpacingToToken function to understand what it returns
rg -A 5 "mapSpacingToToken\s*=" packages/storyblok-ui/src/components/container/

Repository: dmnktoe/httpjpg

Length of output: 41


🏁 Script executed:

# Search for all responsive spacing patterns in the file using || operator
rg -n "\|\|" packages/storyblok-ui/src/components/container/SbContainer.tsx

Repository: dmnktoe/httpjpg

Length of output: 1572


🏁 Script executed:

# Find the spacing-utils file
find packages/storyblok-ui -name "*spacing*" -type f

Repository: dmnktoe/httpjpg

Length of output: 157


🏁 Script executed:

# Also search for the file with ripgrep
rg -l "spacing-utils" packages/storyblok-ui

Repository: dmnktoe/httpjpg

Length of output: 862


🏁 Script executed:

# Read the spacing-utils file to understand mapSpacingToToken
cat -n packages/storyblok-ui/src/lib/spacing-utils.ts

Repository: dmnktoe/httpjpg

Length of output: 6133


🏁 Script executed:

# Also check the test file to see expected behavior
cat -n packages/storyblok-ui/src/lib/spacing-utils.test.ts

Repository: dmnktoe/httpjpg

Length of output: 2575


🏁 Script executed:

# Find and read the constants file
find packages/storyblok-ui -name "constants*" -type f

Repository: dmnktoe/httpjpg

Length of output: 101


🏁 Script executed:

# Read the constants file
cat -n packages/storyblok-ui/src/lib/constants.ts

Repository: dmnktoe/httpjpg

Length of output: 2496


Use nullish coalescing operator so "0" tokens remain valid at md/lg breakpoints.

When mapSpacingToToken returns "0" (the string token for "none" spacing), the || operator treats it as falsy and falls back to base spacing instead. This occurs across all nine responsive spacing blocks (px, py, pt, pb, mt, mb, my, ml, mr). Switch to ?? to only replace on null/undefined:

🔧 Apply to all responsive spacing blocks
-          md: mapSpacingToToken(ptMd) || ptValue,
-          lg: mapSpacingToToken(ptLg) || mapSpacingToToken(ptMd) || ptValue,
+          md: mapSpacingToToken(ptMd) ?? ptValue,
+          lg: mapSpacingToToken(ptLg) ?? mapSpacingToToken(ptMd) ?? ptValue,

Repeat similarly for: responsivePx, responsivePy, responsivePb, responsiveMt, responsiveMb, responsiveMy, responsiveMl, responsiveMr.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const responsivePt =
ptMd || ptLg
? filterUndefined({
base: ptValue,
md: mapSpacingToToken(ptMd) || ptValue,
lg: mapSpacingToToken(ptLg) || mapSpacingToToken(ptMd) || ptValue,
})
const responsivePt =
ptMd || ptLg
? filterUndefined({
base: ptValue,
md: mapSpacingToToken(ptMd) ?? ptValue,
lg: mapSpacingToToken(ptLg) ?? mapSpacingToToken(ptMd) ?? ptValue,
})
🤖 Prompt for AI Agents
In `@packages/storyblok-ui/src/components/container/SbContainer.tsx` around lines
133 - 139, The responsive spacing construction uses the || operator which treats
the string "0" as falsy and incorrectly falls back to base values; update each
responsive spacing block (responsivePt, responsivePx, responsivePy,
responsivePb, responsiveMt, responsiveMb, responsiveMy, responsiveMl,
responsiveMr) to use the nullish coalescing operator (??) when combining
mapSpacingToToken(...) results with fallbacks so that valid "0" tokens are
preserved (e.g., replace occurrences of "mapSpacingToToken(...) || ptValue" with
"mapSpacingToToken(...) ?? ptValue" in SbContainer.tsx).

Comment on lines +144 to +187
const buildGridRow = (spanVal?: string): string | undefined => {
const span = parseRowSpan(spanVal);
return span ? `span ${span}` : undefined;
};

// Build responsive CSS object - only include values that exist
const responsiveGridColumn = hasResponsiveValues
? {
base: buildGridColumn(colStart, colSpan, colEnd),
...(colStartMd || colSpanMd || colEndMd
? { md: buildGridColumn(colStartMd, colSpanMd, colEndMd) }
: {}),
...(colStartLg || colSpanLg || colEndLg
? { lg: buildGridColumn(colStartLg, colSpanLg, colEndLg) }
: {}),
}
: undefined;

const responsiveGridRow = hasResponsiveValues
? {
base: buildGridRow(rowSpan),
...(rowSpanMd ? { md: buildGridRow(rowSpanMd) } : {}),
...(rowSpanLg ? { lg: buildGridRow(rowSpanLg) } : {}),
}
: undefined;

const responsiveCss = hasResponsiveValues
? css({
...(responsiveGridColumn && { gridColumn: responsiveGridColumn }),
...(responsiveGridRow && { gridRow: responsiveGridRow }),
})
: undefined;

return (
<>
{responsiveStyles}
<GridItem
{...editableProps}
colSpan={hasPositioning ? undefined : baseColSpan}
rowSpan={hasPositioning ? undefined : baseRowSpan}
colStart={parsePosition(colStart)}
colEnd={parsePosition(colEnd)}
rowStart={parsePosition(rowStart)}
rowEnd={parsePosition(rowEnd)}
data-sb-grid-item={blok._uid}
>
{content && content.length > 0 && (
<DynamicRender data={content as BlokItem[]} />
)}
</GridItem>
</>
<GridItem
{...editableProps}
{...(!hasResponsiveValues && {
colSpan: baseColSpan,
rowSpan: baseRowSpan,
colStart: parsePosition(colStart),
colEnd: parsePosition(colEnd),
rowStart: parsePosition(rowStart),
rowEnd: parsePosition(rowEnd),
})}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Row start/end are ignored when any responsive values are set.

When hasResponsiveValues is true, base props aren’t passed and responsiveGridRow only uses rowSpan, so rowStart/rowEnd from Storyblok are silently dropped. This changes layout behavior as soon as any responsive column/row value is present.

🐛 Proposed fix (preserve rowStart/rowEnd in responsive mode)
-  const buildGridRow = (spanVal?: string): string | undefined => {
-    const span = parseRowSpan(spanVal);
-    return span ? `span ${span}` : undefined;
-  };
+  const buildGridRow = (
+    startVal?: string,
+    spanVal?: string,
+    endVal?: string,
+  ): string | undefined => {
+    const start = parsePosition(startVal);
+    const span = parseRowSpan(spanVal);
+    const end = parsePosition(endVal);
+    if (start && end) {
+      return `${start} / ${end}`;
+    }
+    if (start && span) {
+      return `${start} / span ${span}`;
+    }
+    if (span) {
+      return `span ${span}`;
+    }
+    if (start || end) {
+      return `${start || "auto"} / ${end || "auto"}`;
+    }
+    return undefined;
+  };
 
-  const responsiveGridRow = hasResponsiveValues
-    ? {
-        base: buildGridRow(rowSpan),
-        ...(rowSpanMd ? { md: buildGridRow(rowSpanMd) } : {}),
-        ...(rowSpanLg ? { lg: buildGridRow(rowSpanLg) } : {}),
-      }
-    : undefined;
+  const responsiveGridRow = hasResponsiveValues
+    ? {
+        base: buildGridRow(rowStart, rowSpan, rowEnd),
+        ...(rowSpanMd
+          ? { md: buildGridRow(rowStart, rowSpanMd, rowEnd) }
+          : {}),
+        ...(rowSpanLg
+          ? { lg: buildGridRow(rowStart, rowSpanLg, rowEnd) }
+          : {}),
+      }
+    : undefined;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const buildGridRow = (spanVal?: string): string | undefined => {
const span = parseRowSpan(spanVal);
return span ? `span ${span}` : undefined;
};
// Build responsive CSS object - only include values that exist
const responsiveGridColumn = hasResponsiveValues
? {
base: buildGridColumn(colStart, colSpan, colEnd),
...(colStartMd || colSpanMd || colEndMd
? { md: buildGridColumn(colStartMd, colSpanMd, colEndMd) }
: {}),
...(colStartLg || colSpanLg || colEndLg
? { lg: buildGridColumn(colStartLg, colSpanLg, colEndLg) }
: {}),
}
: undefined;
const responsiveGridRow = hasResponsiveValues
? {
base: buildGridRow(rowSpan),
...(rowSpanMd ? { md: buildGridRow(rowSpanMd) } : {}),
...(rowSpanLg ? { lg: buildGridRow(rowSpanLg) } : {}),
}
: undefined;
const responsiveCss = hasResponsiveValues
? css({
...(responsiveGridColumn && { gridColumn: responsiveGridColumn }),
...(responsiveGridRow && { gridRow: responsiveGridRow }),
})
: undefined;
return (
<>
{responsiveStyles}
<GridItem
{...editableProps}
colSpan={hasPositioning ? undefined : baseColSpan}
rowSpan={hasPositioning ? undefined : baseRowSpan}
colStart={parsePosition(colStart)}
colEnd={parsePosition(colEnd)}
rowStart={parsePosition(rowStart)}
rowEnd={parsePosition(rowEnd)}
data-sb-grid-item={blok._uid}
>
{content && content.length > 0 && (
<DynamicRender data={content as BlokItem[]} />
)}
</GridItem>
</>
<GridItem
{...editableProps}
{...(!hasResponsiveValues && {
colSpan: baseColSpan,
rowSpan: baseRowSpan,
colStart: parsePosition(colStart),
colEnd: parsePosition(colEnd),
rowStart: parsePosition(rowStart),
rowEnd: parsePosition(rowEnd),
})}
const buildGridRow = (
startVal?: string,
spanVal?: string,
endVal?: string,
): string | undefined => {
const start = parsePosition(startVal);
const span = parseRowSpan(spanVal);
const end = parsePosition(endVal);
if (start && end) {
return `${start} / ${end}`;
}
if (start && span) {
return `${start} / span ${span}`;
}
if (span) {
return `span ${span}`;
}
if (start || end) {
return `${start || "auto"} / ${end || "auto"}`;
}
return undefined;
};
// Build responsive CSS object - only include values that exist
const responsiveGridColumn = hasResponsiveValues
? {
base: buildGridColumn(colStart, colSpan, colEnd),
...(colStartMd || colSpanMd || colEndMd
? { md: buildGridColumn(colStartMd, colSpanMd, colEndMd) }
: {}),
...(colStartLg || colSpanLg || colEndLg
? { lg: buildGridColumn(colStartLg, colSpanLg, colEndLg) }
: {}),
}
: undefined;
const responsiveGridRow = hasResponsiveValues
? {
base: buildGridRow(rowStart, rowSpan, rowEnd),
...(rowSpanMd
? { md: buildGridRow(rowStart, rowSpanMd, rowEnd) }
: {}),
...(rowSpanLg
? { lg: buildGridRow(rowStart, rowSpanLg, rowEnd) }
: {}),
}
: undefined;
const responsiveCss = hasResponsiveValues
? css({
...(responsiveGridColumn && { gridColumn: responsiveGridColumn }),
...(responsiveGridRow && { gridRow: responsiveGridRow }),
})
: undefined;
return (
<GridItem
{...editableProps}
{...(!hasResponsiveValues && {
colSpan: baseColSpan,
rowSpan: baseRowSpan,
colStart: parsePosition(colStart),
colEnd: parsePosition(colEnd),
rowStart: parsePosition(rowStart),
rowEnd: parsePosition(rowEnd),
})}
🤖 Prompt for AI Agents
In `@packages/storyblok-ui/src/components/grid-item/SbGridItem.tsx` around lines
144 - 187, The responsive handling drops rowStart/rowEnd because buildGridRow
only returns a span and responsiveGridRow only uses rowSpan; update row handling
so responsiveGridRow preserves start/end values: create/modify a function (e.g.,
buildGridRow or buildGridRowValue) that accepts rowStart, rowSpan, rowEnd (use
parsePosition for start/end and parseRowSpan for span) and returns either "start
/ end" when start/end exist or "span N" when only span exists; then change
responsiveGridRow to call that function for base/md/lg (pass
rowStart/rowSpan/rowEnd, rowStartMd/rowSpanMd/rowEndMd,
rowStartLg/rowSpanLg/rowEndLg) and keep responsiveCss/GridItem usage the same so
rowStart/rowEnd are preserved in responsive mode.

Comment on lines 56 to 63
style={{
color: mappedColor || "black",
...(mappedColor && { color: mappedColor }),
...(fontSize && { fontSize }),
...(lineHeight && { lineHeight }),
...(fontWeight && { fontWeight }),
...(opacity && { opacity }),
...(letterSpacing && { letterSpacing }),
}}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine the SbHeadline.tsx file
cat -n packages/storyblok-ui/src/components/headline/SbHeadline.tsx

Repository: dmnktoe/httpjpg

Length of output: 2378


🏁 Script executed:

# Check for Panda configuration in the project
find . -maxdepth 3 -type f \( -name "panda.config.*" -o -name "pandaConfig*" \) | head -20

Repository: dmnktoe/httpjpg

Length of output: 88


🏁 Script executed:

# Look for Panda imports or setup in package.json
cat package.json | grep -A 5 -B 5 "panda"

Repository: dmnktoe/httpjpg

Length of output: 41


🏁 Script executed:

# Check if there's tsconfig or other config that might indicate Panda setup
find packages/storyblok-ui -maxdepth 2 -type f -name "*.config.*" | head -20

Repository: dmnktoe/httpjpg

Length of output: 41


🏁 Script executed:

# Find the Headline component
find packages/ui -type f -name "*.ts*" | xargs rg -l "export.*Headline" | head -10

Repository: dmnktoe/httpjpg

Length of output: 152


🏁 Script executed:

# Check the panda config
cat packages/ui/panda.config.ts

Repository: dmnktoe/httpjpg

Length of output: 19692


🏁 Script executed:

# Look at the directory structure of the ui package
ls -la packages/ui/src/components/ | head -20

Repository: dmnktoe/httpjpg

Length of output: 1187


🏁 Script executed:

# Check if there's a Headline component
find packages/ui/src -name "*[Hh]eadline*" -o -name "*[Hh]ead*"

Repository: dmnktoe/httpjpg

Length of output: 276


🏁 Script executed:

# Look at the Headline component implementation
cat -n packages/ui/src/components/headline/headline.tsx

Repository: dmnktoe/httpjpg

Length of output: 4868


🏁 Script executed:

# Check the index to see what's exported
cat packages/ui/src/components/headline/index.ts

Repository: dmnktoe/httpjpg

Length of output: 117


🏁 Script executed:

# Look at the tokens package to understand the typography tokens
find packages -name "*tokens*" -type d

Repository: dmnktoe/httpjpg

Length of output: 74


🏁 Script executed:

# Check if there's a tokens package and what typography is exported
find . -path "*/packages/tokens*" -type d | head -5

Repository: dmnktoe/httpjpg

Length of output: 124


🏁 Script executed:

# Check the tokens package to see what typography tokens are available
cat packages/tokens/src/index.ts 2>/dev/null || ls -la packages/tokens/src/

Repository: dmnktoe/httpjpg

Length of output: 1455


🏁 Script executed:

# Look at the mapping utility functions
cat packages/storyblok-ui/src/lib/token-mapping.ts

Repository: dmnktoe/httpjpg

Length of output: 9381


🏁 Script executed:

# Check if there are any mapping functions for typography
find packages/storyblok-ui/src/lib -type f -name "*.ts" | xargs ls -la

Repository: dmnktoe/httpjpg

Length of output: 653


🏁 Script executed:

# Check what values are actually passed in the CMS/data
rg "fontSize|lineHeight|fontWeight" packages/storyblok-ui/src --type ts --context 3 | head -50

Repository: dmnktoe/httpjpg

Length of output: 3955


🏁 Script executed:

# Check SbParagraph to see how it handles typography props
cat -n packages/storyblok-ui/src/components/paragraph/SbParagraph.tsx | head -80

Repository: dmnktoe/httpjpg

Length of output: 2720


🏁 Script executed:

# Check if mapFontSizeToToken and other typography mappers are actually used anywhere
rg "mapFontSizeToToken|mapFontWeightToToken|mapLineHeightToToken|mapLetterSpacingToToken" packages/storyblok-ui/src --type ts

Repository: dmnktoe/httpjpg

Length of output: 1401


🏁 Script executed:

# Look at the full typography tokens to understand the structure
cat packages/tokens/src/typography.ts 2>/dev/null || ls -la packages/tokens/src/

Repository: dmnktoe/httpjpg

Length of output: 1364


🏁 Script executed:

# Check the Paragraph component to see how it handles css prop
cat -n packages/ui/src/components/paragraph/paragraph.tsx | head -100

Repository: dmnktoe/httpjpg

Length of output: 2973


🏁 Script executed:

# Check if there's any documentation on how Panda CSS css prop works vs style prop
rg "css\?.*SystemStyleObject|style\?.*CSSProperties" packages/ui/src/components -A 2 -B 2 | head -40

Repository: dmnktoe/httpjpg

Length of output: 2236


🏁 Script executed:

# Verify how values flow through - check if any mapping is actually applied in practice
cat packages/storyblok-ui/src/components/headline/SbHeadline.tsx

Repository: dmnktoe/httpjpg

Length of output: 1853


Move typography props to css prop for Panda token resolution.

Inline style bypasses Panda CSS, so token names like "sm", "bold", etc. won't resolve. These should be in the css prop alongside the spacing props so Panda can resolve them to their actual token values.

Suggested fix
    <Headline
      {...editableProps}
      level={level}
-     style={{
-       ...(mappedColor && { color: mappedColor }),
-       ...(fontSize && { fontSize }),
-       ...(lineHeight && { lineHeight }),
-       ...(fontWeight && { fontWeight }),
-       ...(opacity && { opacity }),
-       ...(letterSpacing && { letterSpacing }),
-     }}
      css={{
+       ...(mappedColor && { color: mappedColor }),
+       ...(fontSize && { fontSize }),
+       ...(lineHeight && { lineHeight }),
+       ...(fontWeight && { fontWeight }),
+       ...(opacity && { opacity }),
+       ...(letterSpacing && { letterSpacing }),
        textAlign: align,
        mt: mapSpacingToToken(marginTop),
        mb: mapSpacingToToken(marginBottom),
        pt: mapSpacingToToken(paddingTop),
        pb: mapSpacingToToken(paddingBottom),
      }}
    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
style={{
color: mappedColor || "black",
...(mappedColor && { color: mappedColor }),
...(fontSize && { fontSize }),
...(lineHeight && { lineHeight }),
...(fontWeight && { fontWeight }),
...(opacity && { opacity }),
...(letterSpacing && { letterSpacing }),
}}
<Headline
{...editableProps}
level={level}
css={{
...(mappedColor && { color: mappedColor }),
...(fontSize && { fontSize }),
...(lineHeight && { lineHeight }),
...(fontWeight && { fontWeight }),
...(opacity && { opacity }),
...(letterSpacing && { letterSpacing }),
textAlign: align,
mt: mapSpacingToToken(marginTop),
mb: mapSpacingToToken(marginBottom),
pt: mapSpacingToToken(paddingTop),
pb: mapSpacingToToken(paddingBottom),
}}
>
🤖 Prompt for AI Agents
In `@packages/storyblok-ui/src/components/headline/SbHeadline.tsx` around lines 56
- 63, In SbHeadline, move typography-related inline styles out of the style prop
and into the component's css prop so Panda can resolve tokens: remove fontSize,
lineHeight, fontWeight, letterSpacing, opacity (and mappedColor/color token
usage) from the style object and add them to the existing css prop merge
alongside spacing props; ensure you preserve any computed/mapped values (e.g.,
mappedColor) when assigning to css and leave style only for non-token,
imperative values (or remove it entirely if unused).

Comment on lines 301 to 344
return (
<SbMediaWrapper
spacingTop={spacingTop}
spacingBottom={spacingBottom}
width={width}
editable={editableProps}
>
<div {...editableProps} className={css({ ...widthStyles })}>
<div
className={
RESPONSIVE_WIDTH_CLASSES[
calculatedWidth as keyof typeof RESPONSIVE_WIDTH_CLASSES
] || RESPONSIVE_WIDTH_CLASSES["100%"]
}
className={css({
px: responsivePx,
py: responsivePy,
pl: responsivePl,
pr: responsivePr,
mt: responsiveMt,
mb: responsiveMb,
my: responsiveMy,
ml: responsiveMl,
mr: responsiveMr,
})}
>
<Image
src={processedSrc}
alt={alt || image.alt || image.title || ""}
copyright={finalCopyright}
copyrightPosition={copyrightPosition}
blurOnLoad={blurOnLoad}
blurDataURL={blurDataURL}
css={{
width: "100%",
maxWidth: "100%",
height: isFullHeight ? "100vh" : "auto",
objectFit: aspectRatio ? "cover" : "none",
aspectRatio: aspectRatio || undefined,
}}
loading={isLoadingEager ? "eager" : "lazy"}
/>
<div
className={
RESPONSIVE_WIDTH_CLASSES[
calculatedWidth as keyof typeof RESPONSIVE_WIDTH_CLASSES
] || RESPONSIVE_WIDTH_CLASSES["100%"]
}
>
<Image
src={processedSrc}
alt={alt || image.alt || image.title || ""}
copyright={finalCopyright}
copyrightPosition={copyrightPosition}
blurOnLoad={blurOnLoad}
blurDataURL={blurDataURL}
css={{
width: "100%",
maxWidth: "100%",
height: isFullHeight ? "100vh" : "auto",
objectFit: aspectRatio ? "cover" : "none",
aspectRatio: aspectRatio || undefined,
}}
loading={isLoadingEager ? "eager" : "lazy"}
/>
</div>
</div>

{caption && <SbCaption data={caption} />}
</SbMediaWrapper>
</div>
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -A 5 '<SbCaption' packages/storyblok-ui/src/components/

Repository: dmnktoe/httpjpg

Length of output: 762


🏁 Script executed:

cd packages/storyblok-ui/src/components && cat -n video/SbVideo.tsx | grep -A 20 -B 20 'SbCaption'

Repository: dmnktoe/httpjpg

Length of output: 2333


🏁 Script executed:

cd packages/storyblok-ui/src/components && cat -n image/SbImage.tsx | grep -A 10 -B 30 'SbCaption'

Repository: dmnktoe/httpjpg

Length of output: 2431


Caption placement is inconsistent with SbVideo.

In SbImage, SbCaption is rendered outside the spacing wrapper (line 342), so spacing props won't affect the gap between the image and caption. In SbVideo, the caption is inside the spacing wrapper (line 260 in SbVideo.tsx), making spacing props apply to both the video and caption.

Move the caption inside the spacing wrapper for consistency, or document why the intentional difference exists.

🤖 Prompt for AI Agents
In `@packages/storyblok-ui/src/components/image/SbImage.tsx` around lines 301 -
344, The caption in SbImage is currently rendered outside the spacing wrapper so
spacing props don't affect the gap between image and caption; update the JSX in
SbImage to move the SbCaption (rendered with <SbCaption data={caption} />)
inside the inner spacing wrapper that contains the Image (the div with
px/py/pl/pr/... responsive styles and the child div using
RESPONSIVE_WIDTH_CLASSES), mirroring SbVideo's structure so the spacing props
apply to both image and caption.

Comment on lines +86 to +100
// Check if className contains Panda CSS grid positioning classes
const hasGridClassInClassName =
className &&
(className.includes("grid-c_") ||
className.includes("grid-r_") ||
className.includes("md:grid-c_") ||
className.includes("lg:grid-c_") ||
className.includes("md:grid-r_") ||
className.includes("lg:grid-r_"));

// Calculate grid positioning
// For simple spans, Panda will use generated utility classes
// For complex positioning (start/end), we use inline styles (unavoidable)
const hasResponsiveGridColumn =
cssProp?.gridColumn !== undefined || hasGridClassInClassName;
const hasResponsiveGridRow =
cssProp?.gridRow !== undefined || hasGridClassInClassName;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Split row/column class detection to avoid dropping colSpan defaults.

Line 86-100: hasGridClassInClassName is used for both column and row. If className only contains grid-r_ (row) utilities, hasResponsiveGridColumn becomes true and gridColumn defaults are skipped, so colSpan/colStart won’t apply. This is a layout regression.

🛠 Proposed fix
-    const hasGridClassInClassName =
-      className &&
-      (className.includes("grid-c_") ||
-        className.includes("grid-r_") ||
-        className.includes("md:grid-c_") ||
-        className.includes("lg:grid-c_") ||
-        className.includes("md:grid-r_") ||
-        className.includes("lg:grid-r_"));
+    const hasGridColumnClassInClassName =
+      className &&
+      (className.includes("grid-c_") ||
+        className.includes("md:grid-c_") ||
+        className.includes("lg:grid-c_"));
+    const hasGridRowClassInClassName =
+      className &&
+      (className.includes("grid-r_") ||
+        className.includes("md:grid-r_") ||
+        className.includes("lg:grid-r_"));

-    const hasResponsiveGridColumn =
-      cssProp?.gridColumn !== undefined || hasGridClassInClassName;
-    const hasResponsiveGridRow =
-      cssProp?.gridRow !== undefined || hasGridClassInClassName;
+    const hasResponsiveGridColumn =
+      cssProp?.gridColumn !== undefined || hasGridColumnClassInClassName;
+    const hasResponsiveGridRow =
+      cssProp?.gridRow !== undefined || hasGridRowClassInClassName;
🤖 Prompt for AI Agents
In `@packages/ui/src/components/grid/grid-item.tsx` around lines 86 - 100, The
current shared flag hasGridClassInClassName causes row-only classes to falsely
mark hasResponsiveGridColumn and drop column defaults; split the detection into
two flags (e.g., hasGridColumnClassInClassName and hasGridRowClassInClassName)
that check className for column-specific tokens ("grid-c_", "md:grid-c_",
"lg:grid-c_") vs row-specific tokens ("grid-r_", "md:grid-r_", "lg:grid-r_"),
then use the column flag with hasResponsiveGridColumn (alongside
cssProp?.gridColumn) and the row flag with hasResponsiveGridRow (alongside
cssProp?.gridRow) so colSpan/colStart defaults are preserved when only row
classes exist.

Comment on lines +42 to +43
/* Inherit font size from parent list */
fontSize: "inherit",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

fontSize: "inherit" in base is unreachable due to default variant.

The comment states the intent is to inherit from the parent list, but this value will always be overridden because:

  1. The component defaults size to "sm" (line 90)
  2. The defaultVariants also specifies size: "sm" (lines 63-65)
  3. All size variants explicitly set fontSize

To actually support inheritance, consider adding an explicit "inherit" variant option or making size optional without a default.

Suggested approach
   variants: {
     size: {
+      inherit: {
+        fontSize: "inherit",
+      },
       sm: {
         fontSize: "sm", // 12px
       },

Then update the prop type and defaults accordingly if inheriting is the desired default behavior.

🤖 Prompt for AI Agents
In `@packages/ui/src/components/list/list-item.tsx` around lines 42 - 43, The base
style's fontSize: "inherit" is never used because the component's size prop
defaults to "sm" via defaultVariants and the prop default; update the component
(list-item.tsx) to either add an explicit "inherit" option to the size variants
(e.g., include a "inherit" variant that leaves fontSize as "inherit" and add
"inherit" to the Size prop type) or make size optional/remove the
defaultVariants size so inheritance is possible by default; also update the prop
typing and any places that rely on defaultVariants (and tests/usage) to reflect
the new optional/default behavior.

Comment on lines +211 to +223
// Don't use color variant if custom color is provided in css prop
const hasCustomColor = cssProp && "color" in cssProp;
const colorVariant = hasCustomColor ? undefined : color;

const styles = cx(
css(paragraphRecipe.raw({ size, align, color, weight, spacing })),
css(
paragraphRecipe.raw({
size,
align,
color: colorVariant,
weight,
spacing,
}),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, locate the paragraph component and its recipe configuration
find . -type f -name "*.ts" -o -name "*.tsx" | grep -i paragraph | head -20

Repository: dmnktoe/httpjpg

Length of output: 174


🏁 Script executed:

# Search for paragraphRecipe definition
rg "paragraphRecipe" --type ts --type tsx -A 5 -B 2

Repository: dmnktoe/httpjpg

Length of output: 86


🏁 Script executed:

# Look for Panda recipe/cva configuration
rg "recipe\(|cva\(" --type ts --type tsx -A 10 | head -50

Repository: dmnktoe/httpjpg

Length of output: 86


🏁 Script executed:

# Find paragraph-related files
find . -name "*paragraph*" -type f | head -20

Repository: dmnktoe/httpjpg

Length of output: 111


🏁 Script executed:

# Search for paragraphRecipe without file type filter
rg "paragraphRecipe" -A 10 -B 2 | head -100

Repository: dmnktoe/httpjpg

Length of output: 1890


🏁 Script executed:

# Search for recipe or cva definitions
rg "recipe\(|paragraphRecipe\s*=" -A 15 | head -150

Repository: dmnktoe/httpjpg

Length of output: 1153


🏁 Script executed:

# Read the full paragraph.tsx file to see the complete recipe definition
cat ./packages/ui/src/components/paragraph/paragraph.tsx

Repository: dmnktoe/httpjpg

Length of output: 5475


🏁 Script executed:

# Search for test files or examples with recipe usage and undefined variants
find . -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "recipe\|cva" 2>/dev/null | head -10

Repository: dmnktoe/httpjpg

Length of output: 41


🏁 Script executed:

# Look for other recipe definitions and how they handle undefined variants
rg "recipe\(" -B 3 -A 30 | grep -A 30 "defaultVariants"

Repository: dmnktoe/httpjpg

Length of output: 41


🏁 Script executed:

# Search for Panda CSS or cva documentation in the repo
find . -name "*.md" -o -name "package.json" | xargs grep -l "panda\|styled-system" 2>/dev/null

Repository: dmnktoe/httpjpg

Length of output: 85


🏁 Script executed:

# Check package.json for Panda/styled-system version
cat ./packages/ui/package.json

Repository: dmnktoe/httpjpg

Length of output: 2003


🏁 Script executed:

# Search for other uses of recipe.raw with undefined or conditional variants
rg "recipe\.raw|cva" -B 5 -A 5 | head -200

Repository: dmnktoe/httpjpg

Length of output: 11060


🌐 Web query:

Panda CSS cva recipe undefined variant defaultVariants behavior

💡 Result:

Short answer

  • Panda's cva recipes use defaultVariants when you do not provide a value for a variant. [1]
  • If you explicitly set a variant to null (CVA behavior), the default is removed for that call. Passing undefined is treated as "not provided" so the recipe falls back to the defaultVariant. [1][2]

Why

  • Panda's recipe/cva docs describe defaultVariants as values applied when a variant isn't provided. [1]
  • The underlying cva/Class Variance Authority API documents that defaultVariants can be removed by setting the variant to null; unspecified/omitted props use the defaults. The library typings treat variant props as optional or nullable for this reason. [2][3]

Example

  • Given:
    const btn = cva({ variants:{ size:{sm:'...',lg:'...'} }, defaultVariants:{ size: 'lg' } })
  • btn() -> uses size = 'lg'. [1]
  • btn({ size: undefined }) -> treated as not provided -> uses 'lg'. [1][2]
  • btn({ size: null }) -> explicitly removes default -> no size variant applied. [2]

Sources
[1] Panda CSS — Recipes / cva documentation.
[2] cva (Class Variance Authority) API / notes on defaultVariants and null.
[3] cva TypeScript typings showing variant props may be nullable/optional.

If you want, I can show a runnable code example or confirm exact behavior for the Panda version you’re using — tell me your Panda/CVA versions.


Use null instead of undefined to suppress color variant defaults.

When hasCustomColor is true, passing color: undefined doesn't suppress the color variant—Panda treats undefined as "not provided" and applies defaultVariants.color: "default" (which includes opacity: 1), overriding your custom color. Use null to explicitly remove the variant and prevent opacity from the recipe affecting custom colors provided via the css prop.

const colorVariant = hasCustomColor ? null : color;
🤖 Prompt for AI Agents
In `@packages/ui/src/components/paragraph/paragraph.tsx` around lines 211 - 223,
The paragraph component currently sets colorVariant to undefined when a custom
color is provided, but Panda treats undefined as "not provided" and applies the
recipe defaultVariants; change the logic that computes colorVariant (used in
paragraphRecipe.raw call) to use null instead of undefined when hasCustomColor
is true so the color variant is explicitly removed and the custom css prop color
isn't overridden by defaultVariants (refer to hasCustomColor, colorVariant, and
paragraphRecipe.raw in the cx/css invocation).

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants