docs/react-v9/contributing/rfcs/react-components/no-action-prop-value-standardization.md
@andrefcdias @Hotell
This RFC aims to standardize what values we use in our components for cases where a prop value results in no action, i.e. defaults that apply no styles.
Currently, the approach we follow for all components is to use a string value like 'off', 'none' or 'default' for default values of a prop. This happens both on cases where the default prop has and doesn't have an effect on the component.
There is no standardization for the naming, resulting in our users needing to read documentation to figure out what to use, as the names used might be compared to CSS keywords (like 'unset'), and requiring specific component knowledge as the user can't reuse this information for other components. It is also misleading to provide a string value that has no actual impact on the component.
This RFC proposes that we leverage the standard JavaScript default, undefined, for attributes instead of a string.
Put simply, when a component has a default state/behavior, it SHOULD HAVE a default value.
Example: Card.appearance has 'filled' | 'filled-alternative' | 'outline' | 'subtle' and is 'filled' by default.
When a component does not have a default state/behavior, it SHOULD NOT HAVE a default value.
Anti-example (current state): Text.font has 'base' | 'monospace' | 'numeric' and defaults to 'base', where 'base' does not apply any styles
Example (proposal): Text.font has 'monospace' | 'numeric' and has no defaults (i.e. undefined)
// Card.types.ts
type CardProps = {
appearance?: 'filled' | 'filled-alternative' | 'outline' | 'subtle';
};
type CardState = {
// Required as we need a value for our hooks to work
appearance: 'filled' | 'filled-alternative' | 'outline' | 'subtle';
};
// useCard.ts
const { appearance = 'filled' /* {...} */ } = props; // Default applied to enforce behavior
const state = {
appearance,
// {...}
};
// useCardStyles.ts
const appearanceLookup = {
filled: styles.filled,
'filled-alternative': styles.filledAlternative,
outline: styles.outline,
subtle: styles.subtle,
} as const;
state.root.className = mergeClasses(
cardClassNames.root,
styles.root,
appearanceLookup[state.appearance],
// {...}
state.root.className,
);
// Text.types.ts
type TextProps = {
font?: 'monospace' | 'numeric';
};
type TextState = {
// Also nullable as the default does not overwrite styles
font?: 'monospace' | 'numeric';
};
// useText.ts
const { font /* {...} */ } = props; // We no longer set a default here
const state = {
font,
// {...}
};
// Before
<Text font={isNumeric ? 'numeric' : 'base'}>
// After
<Text font={isNumeric ? 'numeric' : undefined}>
Props and State shapeundefined can't be used as an index typeundefined:
<Text font={isNumeric ? 'numeric' : undefined}>
Using a specific keyword for all possible scenarios would be difficult as there isn't a word that is neutral and appropriate for all the different cases.
none can be confused with CSS's noneoff does not fit cases like an align propertydefault is ambiguous and does not convey the fact that nothing happensEven if we can get a word that fits all the cases, the user would still need to add every single property when consuming a styles hook, like the example shown in the Problem Statement.