Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI Element Attribute #10146

Open
MrMatthewLayton opened this issue Feb 19, 2024 · 5 comments
Open

UI Element Attribute #10146

MrMatthewLayton opened this issue Feb 19, 2024 · 5 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest

Comments

@MrMatthewLayton
Copy link

MrMatthewLayton commented Feb 19, 2024

The purpose of this issue is to formally propose the introduction of a new global element attribute named ui-*. This attribute is designed to offer an alternative or potentially complementary approach to utilising the class attribute for the styling of HTML elements.

Problem Statement

There are many design systems in existence that are implemented in (but not limited to) CSS; for example, Bootstrap, Semantic UI, Radix UI, Material Design, Fluent Design, UIkit, Tailwind, etc.

Most, if not all of these design systems provide front-end developers with a comprehensive set of CSS classes that implement the design system.

The following example illustrates the use of Bootstrap’s CSS classes to create a card, with a header, and a body containing some content:

<div class="card">
  <div class="card-header">
    Header text here
  </div>
  <div class="card-body">
    <h5 class="card-title">Title text here</h5>
    <p class="card-text">Text here</p>
    <a href="#" class="btn btn-primary">Action</a>
  </div>
</div>

Whilst this example is fairly trivial, it highlights the following:

  • Every element requires a class attribute.
  • CSS classes are used instead of semantic elements; for example, card-header could be replaced with header, and card-body could be replaced with section, and styled accordingly. I suspect that this would break due to the nature of cascading styles; i.e. if the card body itself contained headers and sections, and it would also depend on whether the semantics made sense in this context, so perhaps this is a moot point?
  • Styling of buttons requires combinatory styles. where btn provides the base style, and btn-primary is effectively a modifier of the base style. These modifiers could be considered as an entry in a finite set enumeration.

The following example illustrates the use of Tailwind’s CSS classes:

<figure class="md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800">
  <img class="w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="/sarah-dayan.jpg" alt="" width="384" height="512">
  <div class="pt-6 md:p-8 text-center md:text-left space-y-4">
    <blockquote>
      <p class="text-lg font-medium">
        “Tailwind CSS is the only framework that I've seen scale
        on large teams. It’s easy to customize, adapts to any design,
        and the build size is tiny.”
      </p>
    </blockquote>
    <figcaption class="font-medium">
      <div class="text-sky-500 dark:text-sky-400">
        Sarah Dayan
      </div>
      <div class="text-slate-700 dark:text-slate-500">
        Staff Engineer, Algolia
      </div>
    </figcaption>
  </div>
</figure>

This example is less trivial and illustrates the following:

  • Almost every element requires a class attribute.
  • There is better use of semantic elements, like figure, blockquote, and figcaption.
  • The nature of Tailwind’s combinatory styles leads to lengthy class attributes which can be difficult to reason about, and therefore maintain; for example: w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto

Whilst the use of CSS classes is (at least currently) the de-facto approach for implementing web-based design systems, they do suffer some drawbacks.

  • The scope and flexibility of CSS selectors becomes limited mostly to a set of classes.
  • The class attribute effectively flattens a list of whitespace-delimited classes into a string.
  • Combinatory classes lead to lengthy class attributes which can become difficult to reason about and maintain, especially for developers who are unfamiliar with the design system.
  • The property/value nature of CSS is lost, or hidden behind classes.
  • It’s not always easy to differentiate between base classes (i.e. btn) and their modifiers (i.e. btn-primary).
  • Programatically, classes either exist in the class list, or they do not; changing a class incurs removing the undesired class, and replacing it with the desired class.

History of Styling Attributes

Before delving into the proposal for a ui-* attribute, it is pertinent, for the sake of completeness, to grasp the historical context of styling attributes and the reasons they fell out of favour. Historically, several attributes were utilised, most of which have since been deprecated in the context of modern web development.

Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes

align

Specifies the horizontal alignment of the element.

Applies to caption>, <col>, <colgroup>, <hr>, <iframe>, <img>, <table>, <tbody>, <td>, <tfoot>, <th>, <thead>, <tr>

background

Specifies the URL of an image file.

Applies to <body>, <table>, <td>, <th>

bgcolor

Specifies the background color of the element.

Applies to <body>, <col>, <colgroup>, <marquee>, <table>, <tbody>, <tfoot>, <td>, <th>, <tr>

border

Specifies the border width.

Applies to <img>, <object>, <table>

color

Specifies the text color using either a named color, or a color specified in the hexadecimal #RRGGBB format.

Applies to <font>, <hr>

height

Specifies the height of elements.

Applies to <canvas>, <embed>, <iframe>, <img>, <input>, <object>, <video>

width

Specifies the width of elements.

Applies to <canvas>, <embed>, <iframe>, <img>, <input>, <object>, <video>

While I might have overlooked some details, a review of the list suggests that the use of these attributes for styling is generally considered undesirable. This is primarily because they are applicable to only a limited set of elements, leading inevitably to either a hybrid styling approach—where CSS is also employed—or the complete abandonment of these attributes in favor of CSS, which offers a more comprehensive and consistent method for styling.

Proposal

Given that the past has demonstrated styling attributes to be undesirable, it may seem an odd proposal to bring them back, however there is a stark difference in how they would work.

Historically, attributes such as align, background, bgcolor, border, color, height, and width would have been reserved by the W3C and/or WHATWG, and would need to be implemented by browser vendors in order to work correctly.

This is where ui-* is different. Essentially, it would work the same way that data-* attributes work today, in that ui-* would essentially just be a reserved attribute which can be used as a CSS selector.

In fact, this can already be done with data-* attributes, but I feel like that muddies the intent of data-* attributes.

Why is this better than using classes?

  • Rather than a flat list of whitespace-delimited classes, the key/value nature of attributes allows for clearer expression of styles.
  • Combinatory styles can be separated by attribute, with well defined values for their style.
  • It aligns more closely with the property/value nature of CSS.
  • It provides a cleaner way to “roll-up” multiple CSS styles into a single property/value pair.
  • It can take advantage of Boolean attributes (that don’t have an associated value).
  • It becomes easier to differentiate between base classes and modifiers.
  • Programatically, attributes can be selected and changed, or removed entirely, rather than having to manipulate the class list.

At this point, some examples may be useful.

First we’ll start with the original Bootstrap card example:

<div class="card">
  <div class="card-header">
		Header text here
  </div>
  <div class="card-body">
    <h5 class="card-title">Title text here</h5>
    <p class="card-text">Text here</p>
    <a href="#" class="btn btn-primary">Action</a>
  </div>
</div>

We could modify this to adopt ui-* attributes like so:

<div ui-card>
  <div ui-card-header>
		Header text here
  </div>
  <div ui-card-body>
    <h5 ui-card-title>Title text here</h5>
    <p ui-card-text>Text here</p>
    <a href="#" ui-button="primary">Action</a>
  </div>
</div>

In this case, there has been a mild reduction in the source size (which, even with our blazing-fast broadband connections, is still beneficial). Most notably is the change from btn btn-primary to ui-button="primary". In my opinion, it looks much cleaner.

We could go further still by introducing semantic elements:

<div ui-card>
  <header>
		Header text here
  </header>
  <section>
    <h5 ui-card-title>Title text here</h5>
    <p ui-card-text>Text here</p>
    <a href="#" ui-button="primary">Action</a>
  </section>
</div>

In this case, ui-card-header and ui-card-body were replaced with header and section elements respectively. Whilst this may not always be desirable, it highlights that ui-* attributes in this context appear to augment existing elements.

Another example I’ve been thinking about is utilising flexbox styles to implement a “stack panel”:

<div ui-stack-panel 
     ui-orientation="vertical" 
     ui-align-content="start center"
     ui-align-items="center">
	<div>One</div>
	<div ui-fill>Two</div>
	<div>Three</div>
</div>

The key here is that rather than bundling display, orientation and alignment classes into a single class list, we can separate them out into distinct, meaningful attributes with well defined values that can be selected accordingly in CSS.

Conclusion

We have identified that the current trend when designing CSS based design systems tends towards classes as the de-facto approach, and that historically, using attributes to style HTML elements has been undesirable, due to their limitations.

In contrast, the ui-* attribute aligns well with modern web design standards and trends. It allows custom attributes to be targeted by CSS in a familiar way, whilst providing clearer semantics and augmentation over HTML elements.

Most importantly, we can already do this today! - there is nothing technical standing in the way of using ui-* attributes, albeit they would currently yield invalid HTML.

Therefore, the proposal is really for nothing more than the reservation of ui-* as an attribute, intended for extensible, CSS compliant styling.

I'm keen to hear your thoughts. Thank you.

@MrMatthewLayton MrMatthewLayton added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Feb 19, 2024
@Malvoz
Copy link
Contributor

Malvoz commented Mar 13, 2024

FYI there's a related proposal by @LeaVerou in w3c/csswg-drafts#10001, although I'm not certain it'd solve all of your use cases.

@LeaVerou
Copy link

First, I love the thought and care that has been put into writing this proposal, props for that!

However, I'm missing a solid value proposition for either authors, spec editors, or implementors that would make the effort to add or use this worth it. Authors have had data- attributes for years that they could have used as style hooks — yet they prefer to use classes. Why do you think that's the case? My own hypotheses about this are here but I think the topic would benefit from more exploration.
However, I would be very surprised if saving them two characters makes that big of a difference.

Perhaps something like this could work if it also comes with conveniences to target these in selectors, or get their values in CSS. But still, why invent a new thing instead of paving the cowpaths and facilitating this for class names?

FWIW, there are also proposals to allow any hyphen-containing attribute, which would trivially allow ui-*as well, but as an authorland convention.

@MrMatthewLayton
Copy link
Author

@Malvoz and @LeaVerou thank you for your feedback. I have read through the linked proposals / comments. There are clearly still a lot of wrinkles in the proposal to iron out.

I do like the idea that any hyphen-containing attribute would be allowed, which immediately solves this proposal. It also aligns well with hyphenated, custom HTML elements.

During my experimentations with this idea, one of the value props I found from a developer perspective, was the potential for IDEs to auto-complete ui-* attributes; for example, how might we correlate this with Bootstrap classes as they exist today?

The following excerpt comes from Bootstrap:

Notation

Spacing utilities that apply to all breakpoints, from xs to xxl, have no breakpoint abbreviation in them. This is because those classes are applied from min-width: 0 and up, and thus are not bound by a media query. The remaining breakpoints, however, do include a breakpoint abbreviation.

The classes are named using the format {property}{sides}-{size} for xs and {property}{sides}-{breakpoint}-{size} for sm, md, lg, xl, and xxl.

Where property is one of:

  • m - for classes that set margin
  • p - for classes that set padding

Where sides is one of:

  • t - for classes that set margin-top or padding-top
  • b - for classes that set margin-bottom or padding-bottom
  • s - (start) for classes that set margin-left or padding-left in LTR, margin-right or padding-right in RTL
  • e - (end) for classes that set margin-right or padding-right in LTR, margin-left or padding-left in RTL
  • x - for classes that set both *-left and *-right
  • y - for classes that set both *-top and *-bottom
  • blank - for classes that set a margin or padding on all 4 sides of the element

Where size is one of:

  • 0 - for classes that eliminate the margin or padding by setting it to 0
  • 1 - (by default) for classes that set the margin or padding to $spacer * .25
  • 2 - (by default) for classes that set the margin or padding to $spacer * .5
  • 3 - (by default) for classes that set the margin or padding to $spacer
  • 4 - (by default) for classes that set the margin or padding to $spacer * 1.5
  • 5 - (by default) for classes that set the margin or padding to $spacer * 3
  • auto - for classes that set the margin to auto

(You can add more sizes by adding entries to the $spacers Sass map variable.)

Now, how does this translate to ui-*? There could be many flavours of this. (I prefer clarity over brevity, even at the expense of a few bytes here and there, however, this is ultimately down to the developer/designer of the design system). Let's take a subset of Bootstrap's classes, specifically padding, and make it work with ui-* attributes:

[ui-padding*="0"] {
  padding: 0;
}

[ui-padding*="1"] {
  padding: 10px;
}

[ui-padding*="2"] {
  padding: 20px;
}

[ui-padding*="bottom:0"] {
  padding-bottom: 0;
}

[ui-padding*="bottom:1"] {
  padding-bottom: 10px;
}

[ui-padding*="bottom:2"] {
  padding-bottom: 20px;
}

[ui-padding*="top:0"] {
  padding-top: 0;
}

[ui-padding*="top:1"] {
  padding-top: 10px;
}

[ui-padding*="top:2"] {
  padding-top: 20px;
}

/* utils */
[ui-bgcolor="red"] {
  background-color: red;
}

[ui-bgcolor="blue"] {
  background-color: blue;
}

We can then style our document like so:

<div ui-bgcolor="red" 
     ui-padding="2">
  padding: all=20px
</div>
<div ui-bgcolor="blue" 
     ui-padding="top:0 bottom:2">
  padding: top=10px, bottom=20px
</div>
<div ui-bgcolor="red" 
     ui-padding="2 bottom:1">
  padding: all=20px, bottom=10px
</div>

JSFiddle here if you want to play with it

At this point, you may be wondering "okay, so where does IDE auto-completion fit in?"

Currently, if you type in class="... your IDE will probably just display every class that it's aware of, so yes, you get auto-completion already, but it's everything and the kitchen sink thrown into one huge list.

The advantage of ui-* is that the moment you type in ui- you should be greeted with an auto-complete list of all ui-* attributes, but not necessarily their modifiers. When you select one, like ui-padding="... for example, then you are greeted with only the permitted modifiers, such as 0, bottom:1, or top:2.

Now, whilst writing this I've become very aware that the WHATWG doesn't/shouldn't care about how IDE vendors implement auto-completion...in all honesty, the argument does feel a bit "tail wagging the dog".

Perhaps a better way of looking at this particular value prop is to go back to "everything and the kitchen sink thrown into one huge list". class="... does not discriminate classes with regards to their purpose; i.e. m-0 (margin), p-5 (padding), card-header (higher level component styling), d-flex (flex layout), etc.

In contrast ui-* does provide that distinction, because it allows for clear separation of things like margin, padding, layout, and their allowed modifiers.

One final criticism before I sign out; I mentioned above "I prefer clarity over brevity, even at the expense of a few bytes here and there"...

This is a lot longer...

<div ui-padding="top:2 bottom:1" ui-margin="0 start:4">

...than this...

<div class="pt-2 pb-1 m-0 ms-4">

In this case, are front-end engineers going to equate clarity with verbosity, leading to markup that is bloated with ui-* attributes, and therefore favour the brevity they are used to when using classes?

In summary, I think that allowing all hyphenated attributes is probably the best approach and clearly has many use-cases, some of which are in existence already. I could envisage a future where design systems are partitioned by their prefix; i.e. rather than ui-*, Bootstrap might adopt bs-*, and Tailwind tw-*, allowing them to coexist without trampling over one another.

@flashymittens
Copy link

flashymittens commented Apr 15, 2024

(random passerby comment) This feels like a self-inflicted problem of trying to bring CSS back into HTML markup. In any case, some of those things could be fixed with full support of attr():

[data-ui-bgcolor] {
  background-color: attr(data-ui-bgcolor color, var(--ui-bgcolor));
}

// Avoid doing that, seriously…

As for things like “ui-padding="top:0 bottom:2"”, at this point it's time to just use style="…", IMO.

@MrMatthewLayton
Copy link
Author

@flashymittens Yes, the more I think about it, this approach for utility classes probably is a worse idea than using classes. Even for more complex scenarios, it's not great. I think it has some use-cases, but probably not enough to warrant the "make ui-* an open attr." idea.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest
Development

No branches or pull requests

4 participants