department-of-veterans-affairs / vets-design-system-documentation

Repository for design.va.gov website
https://design.va.gov
40 stars 61 forks source link

Discovery: CSS custom properties system to allow precision customization of web components #1523

Closed jamigibbs closed 1 year ago

jamigibbs commented 1 year ago

Description

There have been several requests over the last few months to add the part attribute to various component elements for the purpose of being able to add custom style those components through the shadow dom.

At the moment the following design system components have part attributes:

Screenshot 2023-02-07 at 11 10 34 AM

There have been a few reasons that those elements have been opened up to external styling like being able to change the font-size of header elements to comply with a11y and designs and adjust an elements width to comply with form design.

But when adding a part attribute, it opens up the element to all style attributes which can potentially create a divergence between the intended style of a component.

Details

The purpose of this discovery is to explore implementing a custom properties system that would allow the styling of only specific elements for specific style attributes (instead of full access to an element with the part attribute).

Is it possible to assign a custom property to a specific element and style attribute that keeps them tightly coupled so that style overrides can only be applied to that specific element style?

For example, if the legend element in va-radio needed to have a flexible font size, the --vads-c-radio-legend-size custom property can be registered in the component and applied to the element that it styles in the component:

:host {
  --vads-c-radio-legend-size: 16px;
}

legend {
  font-size: var(--vads-c-radio-legend-size);
}

Then when a team is implementing the component, they assign a class to the component and override the custom property as needed:

<va-radio class="my-custom-class"></va-radio>

.my-custom-class {
   --vads-c-radio-legend-size: 18px;
}

The intention here is that the --vads-c-radio-legend-size custom property will only affect a) the legend and b) the font-size of the legend within va-radio.

A system that does this for comparison is the Salesforce Lightning Design System which calls this approach as "Styling Hooks".

Tasks

Acceptance Criteria

caw310 commented 1 year ago

This will carry over into the next sprint.

caw310 commented 1 year ago

This will carry over into the next sprint

jamigibbs commented 1 year ago

va-button

component library usage

Used in va-button-pair

va-button::part(button) {
  width: 280px;
}

:host([continue]:not([continue='false'])) va-button + va-button::part(button) {
  margin-left: 12px;
  margin-right: 0;
}

https://github.com/department-of-veterans-affairs/component-library/blob/main/packages/web-components/src/components/va-button-pair/va-button-pair.css

vets-website usage

    va-button::part(button) {
      height: 4.2rem;
      width: fit-content;
      min-width: 78px;
      margin: 3px;
    }

https://github.com/department-of-veterans-affairs/vets-website/blob/main/src/applications/mhv/secure-messaging/sass/message-list.scss#L19-L25

  va-button::part(button) {
    height: 36px;
    width: 100%;
    margin: 0;
    @media (min-width: $medium-screen) {
      width: 197px;
    }
  }

https://github.com/department-of-veterans-affairs/vets-website/blob/main/src/applications/mhv/secure-messaging/sass/compose.scss#L196-L203

va-radio

component library usage

None

vets-website usage

header

  va-radio::part(header) {
    font-family: "Bitter", "Georgia", "Cambria", "Times New Roman", "Times", serif;
    line-height: 1.3;
    font-size: 2rem;
    font-weight: 700;
  }

required

  va-radio::part(required) {
    font-family: "Source Sans Pro", "Helvetica Neue", "Helvetica", "Roboto", "Arial", sans-serif;
    font-size: 1.5rem;
    font-weight: 400;
  }

legend

  /* Make radio group legend the same size as an h3 */
  va-radio::part(legend) {
    font-size: 2rem;
    font-weight: 700;
  }

https://github.com/department-of-veterans-affairs/vets-website/blob/main/src/applications/appeals/995/sass/995-supplemental-claim.scss

va-select

component library usage

  :host([error]:not([error=''])) va-select::part(select){
    border: 4px solid var(--color-secondary-dark);
  }

  va-select::part(label) {
    margin-top: 0;
  }

https://github.com/department-of-veterans-affairs/component-library/blob/main/packages/web-components/src/components/va-date/va-date.css

vets-website usage

None

va-text-input

component library usage

  :host([error]:not([error=''])) va-text-input::part(input) {
    border: 4px solid var(--color-secondary-dark);
  }

  va-text-input::part(label) {
    margin-top: 0;
  }

https://github.com/department-of-veterans-affairs/component-library/blob/main/packages/web-components/src/components/va-date/va-date.css

vets-website usage

None

va-textarea

component library usage

None

vets-website usage

None

jamigibbs commented 1 year ago

Discovery Overview (the TL;DR)

How widespread of a problem is overriding styles with part in vets-website?

Considering that part opens up the potential for all styles to be overridden for that element, it's been minimal. We would essentially just be guarding against the possibility of a design system divergence and not necessarily fixing any major divergences today.

The most egregious example that I found might just be resizing the va-button component height found here:

    va-button::part(button) {
      height: 4.2rem;
      width: fit-content;
      min-width: 78px;
      margin: 3px;
    }

https://github.com/department-of-veterans-affairs/vets-website/blob/main/src/applications/mhv/secure-messaging/sass/message-list.scss#L19-L25

Screenshot 2023-03-20 at 10 38 43 AM

What would we need to do to remove part and replace with custom properties?

Decide which styles to allow The first thing we'd need to do is evaluate which CSS properties we want to allow to be the styled. There are several within the component library itself that we would want ( for example va-button width and margin) but others we might not want to allow access to any longer (for example va-button height).

Naming convention We would want to document the naming convention of the custom properties for consistency. For example, Styling Hooks are named with the following convention:

--[namespace]-[scope]-[component]-[element]-[category]-[property]-[attribute]-[state]

An example of how we could name ours based on that model:

// Namespace + Scope + Component + Category + Property
--vads-c-button-sizing-height

Remove part from components and replace with custom properties + documentation This would just simply be removing any part attributes from the components and replacing it with mapped custom properties to specific approved styles.

We will probably also want to update Storybook so that it displays those custom properties somehow as well as create a section for developers in design.va.gov with an example of how to use the custom properties. Maybe something like this:

Screenshot 2023-03-20 at 11 07 57 AM

Update vets-website Last, we'd need to replace the current style overrides with its mapped custom property (or remove the unapproved custom style completely). This is a task DST can likely own since the there isn't an overwhelming amount of part style overrides currently.

What about USWDS v3 variations?

The same custom properties could be applied to v3 classes/elements:

:host  {
  --vads-c-button-sizing-width: auto;
}

/* v3 */
.usa-button {
  width: var(--vads-c-button-sizing-width);
}

/* v1 */
:host(:not([uswds])) button {
    width: var(--vads-c-button-sizing-width);
}

But it's possible the default styles might be different between v1 and v3. In that situation, there would need to be a different custom property for each which could cause additional overhead for teams when they switch to the v3 version of components.