An opinionated styleguide concentrating essential props, common behaviors and accessibility guidelines for various components implemented by React.
The purpose is to guide developers in a clear and cohesive way when specifying design systems' components or applicative ones.
This section lists components by familiar categories. Each category specifies likely needed props (that might be inherited from other components or behavior compositions), accessibility guidelines and prominent extensions of this kind of component.
interface BaseProps<T> {
id?: string;
name?: string;
className?: string;
ref?: React.RefObject<T>;
ariaLabel?: string;
ariaLabelBy?: string;
ariaDescribedBy?: string;
}
- Allow the consumer passing
aria-label
,aria-labeledby
andaria-describedby
in order to label the element and describe it with extra information.
interface ButtonProps extends BaseProps<HTMLButtonElement>, ClickProps, FocusProps {
children: React.ReactNode;
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
prefix?: React.ReactNode;
}
Extending: Base, Click, Focus.
- When the button isn't implemented using the native
<button>
element,role="button"
is needed. - Non-textual buttons (e.g Toggle Button) should have a descriptive title - whether implemented using the native
title
attribute or a custom tooltip.
interface ToggleButtonProps extends ButtonProps {
title: string;
selected?: boolean;
}
interface InputProps<T> extends BaseProps<T>, ChangeProps<T>, ErrorProps<T>, FocusProps {
value?: any;
size?: 'small' | 'medium' | 'large';
disabled?: boolean;
required?: boolean;
}
Extending: Base, Change, Error, Focus.
- Text Field must have label associated with the input using a
for
attribute. - In case of error validation,
aria-describedby
should be associated with the error message element andaria-invalid
should be attached to the input element.
interface TextFieldProps extends InputProps<HTMLInputElement> {
label: string;
placeholder?: string;
readOnly?: boolean;
minLength?: number;
maxLength?: number;
autocomplete?: any;
}
interface CheckboxProps extends InputProps<HTMLInputElement> {
checked?: boolean;
}
interface RadioProps extends InputProps<HTMLInputElement> {
checked?: boolean;
}
interface SelectProps extends InputProps<HTMLSelectElement>, OpenProps {
children: React.ReactNode;
placeholder?: string;
multiple?: boolean;
native?: boolean;
}
interface OptionProps extends BaseProps {
value: any;
children?: React.ReactNode;
disabled?: boolean;
}
interface ModalProps extends BaseProps<HTMLDialogElement>, OpenProps {
children: React.ReactNode;
}
- When the modal isn't implemented using the native
<dialog>
element,role="dialog"
andaria-modal="true"
are needed. - If a close button exits, it should be allowed to pass
aria-label
andaria-labeledby
from the consumer to that button.
interface DialogProps extends ModalProps {
title?: string;
actions?: React.ReactNode;
}
interface LinkProps extends BaseProps<HTMLLinkElement> {
children: React.ReactNode;
href: string;
rel?: string;
target?: string;
}
Extending: Base.
- Pressing the enter key when the link is focused should activate it.
This section lists compositions of common behaviors, so that each behavior consists of several likely needed props and may have public methods.
interface ChangeProps<T> {
value?: any;
onChange?: (event: ChangeEvent<T>) => void;
}
interface ClickProps {
onClick?: React.MouseEventHandler;
onDblClick?: React.MouseEventHandler;
}
interface ErrorProps<T> {
error?: boolean;
errorMessage?: string;
onError?: (event: React.SyntheticEvent<T>) => void;
}
interface FocusProps<T> {
// Consider `autoFocus` carefully - since it may confuse screen readers and real users as well.
autoFocus?: boolean;
onFocus?: (event: React.FocusEvent) => void;
onBlur?: (event: React.FocusEvent) => void;
}
interface FocusMethods {
// Sometimes programmatically managing focus is inevitable and necessary.
focus: () => void;
}
interface OpenProps {
open?: boolean;
onOpen?: () => void;
onClose?: () => void;
}
interface OpenMethods {
// Apparently most of the time the component will be competently controlled,
// but in some cases we might need to open it programmatically.
open: () => void;
}