You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Consider multiple abstraction levels - May make sense to implement simple standalone components and then compose them in opinionated ways. Consider offering both low and high level abstractions, as inspired by the visualization libraries post above.
Rigid vs Flexible tradeoffs:
Rigid benefits:
Enforces consistency
Encourages teams to reach out so we can systematize customizations by adding props
Fewer props = simpler, but can lead to proliferation of props if we keep adding ad-hoc props for styling.
Less duplication risk
Can programmatically enforce best-practices such as accessibility
Building each component takes longer, since we have to carefully try to cover all scenarios
Flexible benefits:
Favors developer autonomy and initial dev speed. Less need to wait for a new component library release to get what you need.
Encourages use. If it's not flexible enough, people just won't use, or will create ugly hacks based on implementation details.
The APIs and styles need not be perfect. Can be more freewheeling.
Developers may rely upon classnames to customize, making DOM changes in the component library risky
If you have a specific design system from your designer, it might be easier and better solution to go with headless components that come unstyled than to adapt a fully featured library's components to your needs. These tools solve the most important accessibility challenges while remaining totally agnostic when it comes to cosmetics and styles:
Have a corp style guide? Need to match existing design?
What sorts of components do you envision yourself needing?
Styling approach? Need to consume styles outside of React?
Documentation
Browser support
Open issues
Consider grabbing a few off the shelf like a datepicker, then build the rest.
Potential Component States
A handy checklist to consider. Typically, it's useful to have a dedicated storybook story for each applicable state below.
Disabled
Initial state (before any interaction on the screen - sometimes just renders empty)
Responsive design
Validation error
No data
Focused
Hover
Lack of permissions
Data changed / pristine / not persisted
Loading state (spinner / skeleton / no render / progress bar...)
Slow connection (consider displaying a "Keep waiting?" message)
Loading timed out - Can also consider a fallback state that displays cached data and a button to retry. Example: A todo's list component fails to load, so you display the todos that were stored in localStorage. The user also sees the message: "The latest data failed to load. Click here to try again."
Action timed out - Example: A save failed. Keep users data on the screen and display a message "Sorry, save failed. Click save to try again."
API call error
Offline - One could be offline, but still be able to write if the component writes to localStorage. Though it's helpful to tell the user their data is being temporarily stored locally until a network connection exists.
Read only
Using a screenreader
Consider and test for different permutations of props
Check Awesome react for libs and standalone components to link, wrap, or fork.
Import style (named vs direct) - Can use named imports without bloating bundle via babel-plugin-transform-imports. But I prefer just setting rollup to generate a js file for each component, and importing directly. Twitter thread.
States - When a user is interacting with a component, what are the different states the entire component or its parts can go through? Common states include:
Hover - The mouse is over the component. Keep in mind this state will never be seen in mobile.
Focus - The component has cursor focus, so typing will affect this component.
Active/Pressed - Usually only visible briefly e.g while the mouse button is pressed.
Selected - Mainly applies to lists or toggle-able elements.
Disabled - The user can't interact with it currently even though it usually is an interactive component.
During dragging - We haven't figured out a general approach here yet.
Error states - Do errors apply to this component? Does it need to catch the user's attention for a critical or patient safety issue?
Content - What is the range of content that a component can take?
Text - Is it only text or can it be anything?
Size - How small or big can it get?
Truncation - If we set a size limit, how do we truncate and indicate to the user that there's content they can't see?
Surroundings - What are the different surroundings a component is likely to be used in and do they affect the design? Common relevant contexts include:
Darker background - Do the component colors need to change?
Limited space (or very large space) - how does the component accommodate space limitations
Mobile - No hover states or cursor in addition to smaller size (not considering much now, but should start)
Co-location - Is this component's relationship to another piece of the UI especially important? If so, what are the variations of that other component to consider?
Animation - When changing states or content, what do the transitions look like?
Layout - Layout transitions like height and width are especially tricky and can impact the work needed to implement.
Speed - Preference for fast or slow? Often this is based on size.
More Design Concerns
Use useId to avoid id collisions. Useful for associating label and input in reusable components.
Make impossible states impossible. Group related props into an object.
Declare contain CSS to improve rendering performance
Honor native API. Accept native HTML props and pass them down to the underlying element. Avoid creating a new API that doesn't honor the plain HTML element.
Avoid the boolean trap - Avoid using booleans that may conflict. Instead, accept an "enum" for mutually exclusive options. Example, accept a buttonType prop with a list of potential string values like primary, secondary, etc, rather than isPrimary, isSecondary.
Watch out for bool props - might be a sign you need an enum. Might also be a sign two separate components would be preferable (much like funcs)
Support "addons" props for TextInput, etc. addons: [{ element: <MyElement/>, position: POSITION.AFTER_INPUT}] or simply belowAddons: <MyElement>.
Different approaches: Accept child components or strings for things like headers on tabs or styled <H1> - <H5> components. Use isValidElement to determine if string or element passed.
Selecting an audience
Be consistent. Example: If you're prepending boolean property names with is or has in one place, do so consistently.
Export an enum for a finite set of props so the consumer can import the enum along with the component and use it, and use the enum in propType declaration for oneOf
Wrap HTML primitives?
Don't rename HTML attributes. Never override HTML attributes in your components. A great example is the element's type attribute. It can be submit (the default), button or reset. However, a lot of developers tend to re-purpose this prop name to mean the visual type of button (primary, cta and so on). By repurposing this prop, you have to add another override to set the actual type attribute, and it only leads to confusion, doubt and sore users.
Whenever your component is passing a prop in your implementation, like a class name or an onClick handler, make sure the external consumer can do the same thing. How:
Use spread when working with a single underlying component. This way you can easily pass all props to it.
Use string concatenation or classnames to combine your classes with the className passed in
Use props.children for more flexibility, and consider creating named "slots" if a single child isn't sufficient. Names slots are useful when the component allows the user to place arbitrary content in multiple spots (such as a card component with different named sections, or a pagelayout component with named header, body and footer slots.
Responsive design, so no mobile-specific features. Example: BottomSlidePanel
Built-in media query - Examples: IconButton, Accordion, Table. Advantage: Easy. Disadvantage: Not configurable. Compromise: Include mobileMaxWidth or isMobile prop.
isMobile prop - isMobile prop - Consumer must set it. Downside: Extra work that people will likely forget. And prop name doesn’t specify what this does. Gotta check the docs. And if the consumer wants only some of the mobile features, they can’t specify. It’s all or nothing. Advantage: Could use this same convention for any components, if we can accept the caveats.
mobileMaxWidth prop - mobileMaxWidth - Advantage: Slightly better than previous option since consumer doesn’t have to read the width to determine when the mobile features should kick in. Disadvantage: All the same as previous.
Well-named prop that describes the specific behavior. Advantage: Can use certain features. Disadvantage: User must wire it up and read the screen width. Example: Avatar. Table density.
Separate mobile component - Advantage: Can lazy load, or not use at all if not relevant. Avoids bloating bundle or slowing loads. And can use on desktop too if relevant (for better or worse). But more work to weave, and many won’t bother. Example: https://material-ui.com/components/steppers/#mobile-stepper
Validate children - only allow certain types of children
constallowedChildren=["string","span","em","b","i","strong"];functionisSupportedElement(child: React.ReactElement){return(allowedChildren.some((c)=>c===child.type)||ReactIs.isFragment(child));}// Only certain child elements are accepted. Recursively check child elements to assure all elements are supported.functionvalidateChildren(children: React.ReactNode){returnReact.Children.map(children,(child)=>{if(!React.isValidElement(child))returnchild;constelementChild: React.ReactElement=child;if(child.props.children)validateChildren(elementChild.props.children);if(!isSupportedElement(elementChild)){thrownewError(`Children of type ${child.type} aren't permitted. Only the following child elements are allowed in Inline Alert: ${allowedChildren.join(", ")}`);}returnelementChild;});}// For performance, only run during developmentif(inDev){validateChildren(children);}
Reusable React Components
My Course - Building Reusable React Components
Lessons Learned Building a React Component Library in Typescript blog post
Consider multiple abstraction levels - May make sense to implement simple standalone components and then compose them in opinionated ways. Consider offering both low and high level abstractions, as inspired by the visualization libraries post above.
Rigid vs Flexible tradeoffs:
Rigid benefits:
Flexible benefits:
Broadly, choose an abstraction level, or perhaps multiple.
Choosing a CSS abstraction level via customization-vs-configuration-in-evolving-design-systems
Questions
Storybook Tips
Picking a Third Party Library
Headless component libraries
If you have a specific design system from your designer, it might be easier and better solution to go with headless components that come unstyled than to adapt a fully featured library's components to your needs. These tools solve the most important accessibility challenges while remaining totally agnostic when it comes to cosmetics and styles:
Reakit - Focused on a11y
Chakra UI - Okay, it has some styles, but listing here since it basically has some lightly styled defaults
Reach UI
Headless UI
Radix UI
react-aria
MUI Base
Lexical - headless editor from Meta, alternatives: Slate, Tiptop
Tanstack Table, TanStack Virtual (similar to react-virtualized, react-window)
Downshift
Riakit / AriaKit
TailwindUI
cmdk
FloatingUI
React dnd-kit
Formik, React Hook Form
https://react-hot-toast.com/
Many more options and context in this slidedeck
Lexical - Rich text editor (WYSIWYG)
How to choose between the libraries above:
Potential Component States
A handy checklist to consider. Typically, it's useful to have a dedicated storybook story for each applicable state below.
Related tweets here and here
Core Decisions
Dev Environment
Documentation
Sandbox / playground / live editing
https://github.com/nihgwu/react-runner
https://github.com/FormidableLabs/react-live
https://github.com/codesandbox/sandpack
Tweet
Design
States - When a user is interacting with a component, what are the different states the entire component or its parts can go through? Common states include:
Content - What is the range of content that a component can take?
Surroundings - What are the different surroundings a component is likely to be used in and do they affect the design? Common relevant contexts include:
Animation - When changing states or content, what do the transitions look like?
More Design Concerns
isPrimary
,isSecondary
.addons: [{ element: <MyElement/>, position: POSITION.AFTER_INPUT}]
or simplybelowAddons: <MyElement>
.<H1>
-<H5>
components. Use isValidElement to determine if string or element passed.is
orhas
in one place, do so consistently.oneOf
Mobile
Mobile friendly component options:
Styling
Testing
Development decisions, tips, and workflow
Deployment
Validate children - only allow certain types of children
More solid tips in this blog post
The text was updated successfully, but these errors were encountered: