Background
Current .cursor/rules/styling.md states:
NEVER store classes in variables - Breaks VSCode Tailwind IntelliSense and linting
However, this rule is ambiguous and the codebase has inconsistent patterns. We need clear guidelines on when to use twClassName vs style with tw.style() in React Native components.
The Problem
What's currently unclear:
- When should components use
twClassName prop?
- When should components use
style prop with tw.style()?
- Are map lookups (const objects) considered "storing in variables"?
- Is storing merged/concatenated class strings acceptable?
Proposed Clarification
✅ twClassName - Pure static strings only
Use twClassName when passing static, unchanging Tailwind classes:
<Box twClassName="rounded-sm border-2 px-4">
<Text>Static styling</Text>
</Box>
✅ style with tw.style() - Dynamic/conditional classes
Use style array pattern with tw.style() when you need:
- Conditionals (pressed, isActive, isDanger, etc.)
- Map lookups (severity → color mapping)
- Merging with user's style prop
// Good: Map lookup (const object is fine)
const borderColorClass = MAP_BANNER_ALERT_SEVERITY_BORDER_COLOR[severity];
return (
<BannerBase
style={[
tw.style(`border-l-4 ${borderColorClass}`),
style, // User's custom style
]}
{...props}
/>
);
❌ Don't store merged/concatenated results
Avoid storing the result of string concatenation/merging:
// ❌ Bad - Stores merged result
const mergedCloseButtonTwClassName = closeButtonTwClassName
? `ml-3 ${closeButtonTwClassName}`
: 'ml-3';
// ✅ Good - Inline in tw.style()
style={[tw.style('ml-3', closeButtonTwClassName), style]}
Components Needing Review
Found these components storing merged/conditional class strings in const variables:
1. BannerBase.tsx (lines 66-68, 77)
// Line 66-68: Stores merged className
const mergedCloseButtonTwClassName = closeButtonTwClassName
? `ml-3 ${closeButtonTwClassName}`
: 'ml-3';
// Line 77: Inline concatenation in prop
twClassName={twClassName ? `rounded-sm ${twClassName}` : 'rounded-sm'}
Suggested fix:
// Use style array pattern
style={[tw.style('ml-3', closeButtonTwClassName), style]}
2. ButtonIcon.tsx (lines 40-46)
// Stores conditional class assignments
const twIconColorClassNames =
variant === ButtonIconVariant.Floating
? 'text-primary-inverse'
: 'text-icon-default';
const borderRadiusClass =
variant === ButtonIconVariant.Default ? 'rounded-lg' : 'rounded-full';
Suggested fix:
// Inline in tw.style()
tw.style(
variant === ButtonIconVariant.Floating
? 'text-primary-inverse'
: 'text-icon-default',
variant === ButtonIconVariant.Default
? 'rounded-lg'
: 'rounded-full',
)
3. HeaderBase.tsx (lines 109-112)
// Stores merged base + user className
const baseStyles = 'flex-row items-center gap-4 h-14';
const resolvedTwClassName = twClassName
? `${baseStyles} ${twClassName}`
: baseStyles;
Suggested fix:
// Use tw.style() in array
style={[
tw.style('flex-row items-center gap-4 h-14', twClassName),
includesTopInset && { marginTop: insets.top },
style,
]}
4. Checkbox.tsx (lines 80-84)
// Stores tw.style() result
const twContainerClassNames = tw.style(
'flex-row items-center',
isDisabled ? 'opacity-50' : 'opacity-100',
twClassName,
);
Note: This stores the computed style object, not a class string. Less clear if problematic, but could be inlined.
Reference Implementation
See BannerAlert component (PR #XXX) for the recommended pattern:
- Uses map lookup for dynamic border color (✅ acceptable)
- Uses
style={[tw.style(...), style]} array pattern
- Keeps classes inline in
tw.style() call
- Passes
twClassName through {...props} to Box
Proposed Rule Updates
Update .cursor/rules/styling.md React Native section with:
-
Clear distinction:
twClassName = static strings only
style + tw.style() = dynamic/conditional/merged
-
Map lookups are fine:
- Const object lookups (MAP_SEVERITY_BORDER) are NOT "storing in variables"
- This is a lookup table pattern, not the anti-pattern
-
Avoid storing merged results:
- Don't store concatenated/merged class strings in const
- Keep classes inline in
tw.style() for IntelliSense
-
Array pattern for style merging:
- Use
style={[tw.style(...), style]} when merging with user style
- Matches Box component foundational pattern
Acceptance Criteria
References
- Current rule: .cursor/rules/styling.md
- BannerAlert refactor:
banner-alert branch
- Research findings: See plan mode conversation for 5 pattern analysis
Background
Current
.cursor/rules/styling.mdstates:However, this rule is ambiguous and the codebase has inconsistent patterns. We need clear guidelines on when to use
twClassNamevsstylewithtw.style()in React Native components.The Problem
What's currently unclear:
twClassNameprop?styleprop withtw.style()?Proposed Clarification
✅ twClassName - Pure static strings only
Use
twClassNamewhen passing static, unchanging Tailwind classes:✅ style with tw.style() - Dynamic/conditional classes
Use
stylearray pattern withtw.style()when you need:❌ Don't store merged/concatenated results
Avoid storing the result of string concatenation/merging:
Components Needing Review
Found these components storing merged/conditional class strings in const variables:
1. BannerBase.tsx (lines 66-68, 77)
Suggested fix:
2. ButtonIcon.tsx (lines 40-46)
Suggested fix:
3. HeaderBase.tsx (lines 109-112)
Suggested fix:
4. Checkbox.tsx (lines 80-84)
Note: This stores the computed style object, not a class string. Less clear if problematic, but could be inlined.
Reference Implementation
See BannerAlert component (PR #XXX) for the recommended pattern:
style={[tw.style(...), style]}array patterntw.style()calltwClassNamethrough{...props}to BoxProposed Rule Updates
Update
.cursor/rules/styling.mdReact Native section with:Clear distinction:
twClassName= static strings onlystyle+tw.style()= dynamic/conditional/mergedMap lookups are fine:
Avoid storing merged results:
tw.style()for IntelliSenseArray pattern for style merging:
style={[tw.style(...), style]}when merging with user styleAcceptance Criteria
.cursor/rules/styling.mdwith clarified guidelinesReferences
banner-alertbranch