Subchapter 2.66
references/editor-react-component/PROPS-VS-CSS.mdMarkdown6 KBView on GitHub
Rules and patterns for handling visual/layout properties that vary by breakpoint.
Is this a content/data property?
│
├─ YES → Can it be derived from other props / internal state?
│ │
│ ├─ YES (subtotal = price × qty, fullName = first + last, etc.)
│ │ └─ ❌ NOT a prop → compute internally
│ │
│ └─ NO (label, items, imageSrc, link, title, etc.)
│ └─ ✅ React prop
│
└─ NO → Would a user want this to vary per breakpoint?
│
├─ YES (showLabel, orientation, displayMode, iconPosition)
│ └─ ❌ NOT a prop → CSS only
│
└─ NO → Is it direction (RTL/LTR)?
│
├─ YES
│ └─ ✅ React prop (internationalization)
│
└─ NO → Is it behavior (disabled, required)?
│
├─ YES
│ └─ ✅ React prop
│
└─ NO
└─ ❌ NOT a prop → CSS onlyComponents must distinguish between content/data and visual/layout properties:
label, items, imageSrc, link, titledisabled, required, searchable, multipleshowLabel, showIcon, displayMode, iconOnlyorientation, alignment, compacthideOnMobile, showOnDesktop, mobileViewProps like showProgressBar, showVolumeControls, showTimeDisplay, showNavigation, showControls are visual display decisions that CANNOT be props.
Why: Show/hide decisions affect layout and users need breakpoint control:
Pattern: These look like “feature toggles” but they’re actually “layout modes”.
Exception: direction (RTL/LTR) is a mandatory prop for internationalization, not a breakpoint-responsive property.
Critical Rule: If a property should be customizable per breakpoint, it CANNOT be a React prop.
Visibility/display properties can be overridden per breakpoint by the user via the editor — but, just like background colors, the component still authors the resting defaults in CSS:
display: none to hide an element by default) — visibility is user-controlled per breakpoint. Layout display (flex, grid, etc.) on layout containers is fine and expected. Resting visual properties (background, color, border-radius, padding, font) DO belong in CSS — see CSS-GUIDELINES.md.// ✅ CORRECT: Always render
<div className={styles.progressBar}>...</div>
<div className={styles.volumeControls}>...</div>
<div className={styles.trackInfo}>...</div>
// SCSS: no `display: none` default — visibility is user-controlled per breakpoint
.progressBar {
position: relative;
}Ask these questions in order:
“Would a user want this to vary per breakpoint?” (mobile vs desktop vs tablet)
“Does this control WHAT is displayed vs WHICH content to display?”
showProgressBar → Controls WHAT (layout decision) → CSS onlyaudioUrl → Specifies WHICH audio file (content) → Prop is OK“Does it change the visual appearance or layout?”
showControls, orientation, alignment → Visual/layout → CSS onlydisabled, required, autoPlay → Behavior (not visual) → Props are OKRule of thumb: If the prop name starts with show*, hide*, display* → It’s CSS-only.
Examples:
direction prop) → Props are OK (not breakpoint-responsive)❌ Wrong — visual properties as props:
interface ButtonProps {
showLabel?: boolean; // ❌ Breakpoint-customizable
showIcon?: boolean; // ❌ Breakpoint-customizable
displayMode?: 'icon-only' | 'label-only' | 'both'; // ❌
iconPosition?: 'left' | 'right'; // ❌ Layout
orientation?: 'horizontal' | 'vertical'; // ❌ Layout
}✅ Correct — only data as props:
interface ButtonProps {
label: string; // ✅ Content data
icon: VectorArt; // ✅ Content data
}❌ Wrong — show/hide as props:
interface AudioPlayerProps {
showProgressBar?: boolean; // ❌ Layout decision
showVolumeControls?: boolean; // ❌ Layout decision
showTimeDisplay?: boolean; // ❌ Layout decision
showTrackInfo?: boolean; // ❌ Layout decision
}
// Conditional rendering
{showProgressBar && <div className={styles.progressBar}>...</div>}✅ Correct — always render, users control visibility:
interface AudioPlayerProps {
audioUrl: string; // ✅ Content data only
title?: string; // ✅ Content data
artist?: string; // ✅ Content data
}
// Always render all elements
<div className={styles.progressBar}>...</div>
<div className={styles.volumeControls}>...</div>
<div className={styles.trackInfo}>...</div>
// SCSS: no `display: none` default — visibility is user-controlled per breakpoint
.progressBar {
position: relative;
}❌ Wrong:
export interface ButtonProps {
showIcon?: boolean; // ❌ Breakpoint-customizable
iconPosition?: 'left' | 'right'; // ❌ Layout
}✅ Correct:
export interface ButtonProps {
label: string;
icon: VectorArt;
// Visual variations via CSS
}