penumbra-zone / web

Apache License 2.0
15 stars 16 forks source link

Move toward a "black box" component library model for `packages/ui` #432

Closed jessepinho closed 2 months ago

jessepinho commented 9 months ago

At the moment, our @penumbra-zone/ui package contains components whose appearance can be highly customized by the devs using them, particularly through the use of the className prop.

Ideally, a component library should be much less customizable. In the same way that an API exposes specific endpoints rather than allow any client to make SQL queries directly against its database, a component library should have a specific set of "endpoints" (in the form of component props) that allow them to be customized in pre-defined, rather than ad-hoc, ways.

The idea is that developers shouldn't generally be making design decisions when working on a given UI ticket, as that leads to poor UX decision-making. Designers are the ones responsible for making sure that an app has visual unity, and that each user flow is optimized to make it as painless and intuitive as possible. Developers, meanwhile, are generally focused on making a feature work, even if it comes at the expense of those two design concerns. Thus, if the design of an app is led by developers, its visual unity will gradually disintegrate over time, as each developer tweaks designs to their own tastes. And, common workflows will often suffer without a designer's research and expertise.

Thus, I am suggesting the following:

  1. Develop a component library + design system in e.g., Figma.
    • This should include enough components that nearly all UI tickets can be completed by combining existing components, rather than creating new ones.
    • The system should be extremely specific. For example:
      • It should specify base layout and spacing units (such as 4px for spacing and e.g., a 12-column grid).
      • Each component should have its internal spatial relationships defined. (External spatial relationships are always determined by the container, not be the component itself. e.g., a button should never have external margins, as those should be set by their containing elements to avoid CSS override hell; but it is permitted to have internal padding.)
    • The system should specify shared values like border radii, brand colors, typefaces, etc. along with what each is used for.
  2. Implement this component library in @penumbra-zone/ui.
    • Customization of these components, beyond what is specified in the Figma designs, should be disabled. For example, if Figma calls for a button with several "variants" like primary, secondary, and icon — each of which have different background colors and sizes — these can be specified via the <Button /> component's variant prop, NOT via a className prop that allows customization of colors and sizes.
    • Most, if not all, of these components should be built from scratch, rather than on top of an existing UI component library. In my experience with component libraries (both off-the-shelf ones, as well as the custom one I implemented from Figma designs for my previous employer), trying to build brand-compliant components on top of existing UI component libraries is unnecessarily painful, with tons of overrides/etc. required to get components to look how they should. It's much simpler and cleaner to build these components from the ground up. The only exception to this rule is when complex accessibility and/or interactivity concerns are at play, such as with a floating dropdown menu.
  3. Sunset our use of Tailwind.
    • Tailwind is designed to make it easy for developers to customize the appearance of components — AKA, exactly what I'm suggesting we don't want.
    • Tailwind classes make it hard to reason about what styles a given element has, as it forces developers to inline all styles for elements, usually in a single line. For example: https://github.com/penumbra-zone/web/blob/main/packages/ui/components/ui/dialog.tsx#L40
    • Instead, we should use Dripsy, an unopinionated library that makes it easy to provide various styles to components via React contexts. (I've had success using this for building a ground-up component library in the past.)
jessepinho commented 9 months ago

cc @grod220 per our discussion on the topic

grod220 commented 9 months ago

Helpful! A few thoughts:

jessepinho commented 9 months ago

@grod220:

There will be some sizable design changes coming down the pipeline...

Yup, makes sense.

The className prop is really a kind of inversion-of-control…

The best way to avoid pain here is: anything that affects the internal elements of a component should be handled by the component itself (with the optional input of limited props like variant). Anything affecting the external relationships between the component and its surroundings should be handled by the container component.

The reason I don’t like e.g., passing position: relative directly to a component’s root element is that it can interfere with a component’s internal styling logic. Let’s say, for example, that we have a

VanishMax commented 2 months ago

"Black box" model was described in #1406, followed by #1411 and implemented later. Closing as done

The future plans of UI package development are moved to #1710