ui icon indicating copy to clipboard operation
ui copied to clipboard

[RFC]: Button as Link or Link as Button, and vice-versa?

Open frassinier opened this issue 2 years ago • 9 comments

Context

We need to display links rendered as buttons and the opposite, for semantic reasons, with minimal effort.

Problems

We used to deal with <Button as={Link} href /> and now they are renamed to <ButtonAsLink href /> in the fresh last new version of the Design System.

buttonAsLink

And we have the same behavior for the Links.

linkAsButton

This naming seems a little bit hard to guess for some of us.

Solutions

We need your feedback on that point. Should we use a better naming? Otherwise, put a simple emoji here so we can keep that naming because... it's ok, and we can move forward.

frassinier avatar Apr 06 '22 14:04 frassinier

I think the current names are clear as we read them exactly the same way as the as props. In your example: <Button as={Link} /> is read Button as Link === <ButtonAsLink /> is read Button as Link (mic drop + kiss)

jsomsanith-tlnd avatar Apr 07 '22 08:04 jsomsanith-tlnd

Hello,

Disclaimer: My experience is...

  • I'm used to use HTML tags and a CSS framework. (ie I'm familiar with the HTML API and then for styling needs, I'm familiar to use a CSS class.)
  • I don't know a lot Talend UI. (ie I may miss some historical decisions/context, for example: the as property. I don't see/understand to what need this attribute is the solution.)

Now, I'm trying to get the whole picture to understand why the current naming proposal and here is my understanding of the current topic: For better developing and maintainability experience, there is a need to have 2 distinct types between "button" and "link" in order to enforce their attribute API:

  • one type for a "button"
    • it doesn't have href in its attributes
    • it is expected to have a type attribute (default to submit or button, as you see fit)
    • it can have a toggle mode maybe?
  • another type for a "link"
    • it would be the goto for specifying an href. And target attribute.
    • it also has a type attribute but with values from mime types.
    • either there is no disable attribute or there is one but it is handled properly.
    • it won't have a toggle mode (not an exhaustive list, there may be other attributes to differentiate these 2 types)

Also, we need:

  • to make those types easy to find by developers.
  • they share some same styles (primary, secondary, ...)

Also, I'd add this constraint: When I set a href to a component, I'd expect it to generate an anchor tag <a/>. (see my HTML bias here...)

Last point: those components are at the lowest levels: molecules (ie very close to HTML... you see me coming ;)

So, this is what I have in mind. And I don't know about other need or constraint that should be included (this is why the RFC is here, to get the whole context of the topic).

So, here is my current thinking:

My current "mental model" is:

  1. want to set a path /the/path? => go for an href: write <a href="/the/path" />
  2. want to look like a primary action? add class name btn-primary.

That's it. Another scenario: I need to change the link for a button because there isn't any navigation involved anymore:

  1. Let's find the link
  2. change the <a/> tag to <button/> tag
  3. update attributes and values accordingly.

So, I'd expect a similar experience with the proposed types:

  1. want to set an href? write <Link href="/" />
  2. want to look like a primary action? add attribute styledAs="PRIMARY" (for example)

And that's it: I know <Link/> will generate an anchor tag, so I know that the HTML output is as expected, and so, with all its native characteristics (the browser navigation will work as expected, etc). Also I already guess most of the attributes <Link /> will offer me or not.

And the same for <Button />, I expect it to have an API close to the <button> HTML and to output a <button> and handles all its states.

=> The mapping is straightforward, no surprise in the api, no surprise in the behaviour, and the api is enforced for each type. (==> why would we need to have another mapping? creating unexpected output? unexpected behaviour? creating more confusion?)

From there, I don't expect another molecule component to offer me the href attribute. And I think this is aligned with the primary need to have 2 distinct types. Also, as seen, the styling requirement is answered with an attribute styledAs (or another explicit naming? for example: as isn't explicit enough: has it an effect on the DOM ouput? is it about style only? what will be the resulting html tag? and even, why should we use this attribute at all? if there is an href, shouldn't it generate a <a/> automatically? if yes, why the element is called Button if it won't generate <button> but <a>? why not a more generic term like Action? but then, how do you enforce distinct API attributes between one case and another? Right now, I can't wrap my head around this strategy).

So, to sum up: just with <Button> and <Link> mapped 1:1 to <button> and <a>, and an attribute for style, looks like it covers criteria listed above.

But, I think I miss some other criteria, what are they?

  • Is there a need to have distinct types for different styles? what is it?
  • Is there any another need? constraint?

Maybe there is a doc about all criteria that need to be taken into account for this topic? If you could provide a link, styled as a link ;), would be helpful.

(sorry for stating whatever could appear as being obvious above)

cc @frassinier @Benrajalu

pgu avatar Apr 07 '22 08:04 pgu

  • <ButtonAsLink href /> has good discoverabilty but in the same time, it's sound wrong to me because under the hood it's not a button but a link that looks like a button.

Element nature should have priority over element UI, so it should be

<LinkAsButton href>
<LinkAsButtonPrimay href>
<LinkAsButtonSecondary href>

and so on...

  • with the same way of thinking, the <LinkAsButton onClick /> is a button and not a link so it shoud be
<ButtonAsLink onClick>
<ButtonPrimaryAsLink onClick>
<ButtonSecondaryAsLink onClick> 

sgendre avatar Apr 07 '22 10:04 sgendre

But still, ButtonAsLink/LinkAsButton? We are at the "atom" level; we know HTML API and we are very close to it. Why a mapping 1:1 between <Button>/<button> and <Link>/<a> with a separation of concern for managing style, isn't enough?

Isn't it a convulted way to produce an anchor tag by using ButtonAsLink? instead of just <Link> or change a <a/> to a <button/> with <LinkAsButton>? instead of just replacing <Link> by <Button>? (combination such as <Link styledAs="PRIMARY">, <Button styledAs="LINK">, ... isn't enough?)

Why do we need them? By extension, should I expect? <GridAsTable/> - <TableAsGrid/> <InputAsSelect/> - <SelectAsInput/> (ex: UIselect, an advanced "select" box, is implemented with an input and divs) (<InputAsTextarea/> ...)

There must be some information I'm missing to understand why at this level (atom), there are these components.

pgu avatar Apr 07 '22 12:04 pgu

@pgu

Some answers to your questions:

  • as is a polymorphic props. It's not specific to the DS or Talend, and it's heavily used in components libraries such as reakit. <Link as={<RouterLink to="/page"}>Page</a> where RouterLink is an import from React-Router or Reach Router for instance would apply the style and behavior of the DS-documented Link to the specified library component. It's something we offer rarely, but we offer nonetheless when it makes sense in context, as it does here.

  • Different styles may have different types then. We don't assume. We consider styles as semantics, and semantics may involve different behaviors. Today we offer a Button with a variant prop that is only there to deliver the proper Button component: ButtonPrimary ButtonDestructive etc... Deleting one such component also then becomes much much easier: we only break one thing, easily identified, not a prop on a very heavily used component.

  • The naming is indeed based on styles (Button as in "it looks like a button") primarily. What matters to us is that the component library is a direct answer to the Figma library. In design, what's a link or a button is not always up to the designer. I may design a CTA thinking it will be a button performing an action on the page, but as a developer you'll actually need it to be an anchor. In both cases, when discussing the UI element, you're going to call that CTA a Button. We really want to push this shared language, this bridge.

Benrajalu avatar Apr 07 '22 14:04 Benrajalu

Ok, thanks @Benrajalu for the pointers and the requirements I was missing.

pgu avatar Apr 08 '22 09:04 pgu

OK, about the as, I can see, this is something like: <Link as="div" ... /> <Button as="div" ... /> (sorry to hurt your eyes ;) from Reakit though >< ) => there is a hierarchy of information: This component (Link or Button) will still look like and behave like it's supposed to, but the HTML tag will be div => no confusion about the as effect.

Now to put this "detail" at the same level of the component name breaks this hierarchy, and, create some confusion, especially with the following fact:

  • Link already means a component = style + behaviour + DOM tag: <a/>
  • Button already means a component = style + behaviour + DOM tag: <button/> so, it's justified that the conjunction LinkAsButton or ButtonAsLink creates confusion about the impact the "As.." may have.

Aren't there other available naming strategies? ex: 1)

  • Link_as_button
  • Button_as_anchor, Button_as_a

(1) as_ + name of the HTML tag (2) avoid same case as existing component, such as Button => it reduces the confusion, and may be closer to the intention of the message we want to convey.

Note: link is intentionally discarded to avoid even more the association to the component Link.

  1. LinkWithDOMButton ButtonWithDOMAnchor ?

  2. any other ideas?

One of this strategy above would create less confusion and still help with all requirements, no?

pgu avatar Apr 08 '22 09:04 pgu

By the way, ButtonPrimaryAsLink then becomes a Linkable, and LinkAsButton becomes a Clickable.

(ButtonAsLink is just a facade, the actual components are standalone variants)

  • Linkable and Clickable a private primitives within the DS.
  • Link is built on top of Linkable. So is ButtonAsLink: both share the same core ruleset (including the as prop).
<Link as={<ReactRouterLink to="pouet" />} data-testId="link-pouet">Pouet</Link>

// is as valid as

<ButtonPrimaryAsLink as={<ReactRouterLink to="pouet" />} data-testId="link-pouet">Pouet</ButtonPrimaryAsLink>

// but crucially: they are not necessarily HTML anchors, because the "as" prop decides what they are
  • All buttons (including ButtonIcon, LinkAsButton etc...) are built on top of Clickable.

Maybe you'd then rather have <LinkableButtonPrimary> and <ClickableLink> (😬 )?

https://github.com/Talend/ui/blob/master/packages/design-system/src/components/Clickable/Clickable.tsx

https://github.com/Talend/ui/blob/master/packages/design-system/src/components/Linkable/Linkable.tsx

Benrajalu avatar Apr 08 '22 12:04 Benrajalu

After a chat with Benoit, it helps understanding that actually, there is a pre-requisite when talking about Design System topics. This pre-requisite is that DS falls within another paradigm of DX.

As mentioned before, my current paradigm of DX is: ("functional" before "style") (0) I need to set an action to go to '/the/path/' (1 - functional) Navigation to a path? then go for an anchor tag <a/> and set its href (2 - style) Then, style it, ie add a classname (primary, secondary, whatever).

And here is the crux of the current discussion: DS is the other way around: ("style" before "functional") (0) I need to set an action to go to '/the/path/' (1 - style) Check with UX persona to see where this action should be placed, etc UX persona can then decide what it should look like. (2 - style) UX personas will express themselves with the UI PoV, (defined in Figma and documented in DS), ex:

"In this page, you'll need to present it as a secondary button."

=> As a frontend developer, I'm aware of their UI PoV, so, "button" actually means an action looking like a button. (3 - functional) As a frontend developer, I know the DOM result should be an anchor. So, my search needs to start with the UI PoV of UX Persona (SecondaryButton), and then, look for the element from my PoV (Link) in the DS lib (link).

Even if I'm not used to it, I can appreciate the fact that:

  • it sets a common vocabulary between participants (UX / DS / Frontend) (and thus, streamline processes (Figma, DS))
    • But, for this to work, as a frontend, I need to be aware that at all moments, they are talking at a UI level.
    • when it comes to implementation, I'll be able to set the proper tag with its capabilities (DS guys have our backs ;) ).
  • it enforces to include the UX persona soon in the loop (either for a new UI or for maintenance).

So, with this pre-requisite in mind, the naming is actually following this DX paradigm. First, what it looks like. Then, the "As" part is about how it should be generated and its impact is never on the UI level.

Component name Component UI result Component DOM tag & API
Link anchor UI anchor tag & anchor-like API
LinkAsButton anchor UI button tag & button-like API
Button button UI button tag & button-like API
ButtonAsLink button UI anchor tag + anchor-like API

Note: An idea to make the prerequisite more "explicit" would be to express it through the naming, ex: UILink, UIButton, ... And we would have for example:

Current New
Link UILinkAsLink
LinkAsButton UILinkAsButton
Button UIButtonAsButton
ButtonAsLink UIButtonAsLink

And here, there isn't a misunderstanding about what we're talking about:

  • UILink refers to UI appearance
  • Button refers to DOM tag and API component There's no confusion with UIButton, which was the problem at the start of this conversation (Button is a UI component in DS, so LinkAsButton what final result should we expect?)

But, all DS components would end up with UI prefix, cluttering the whole lib component names. So, as it concerns few components and now that the expected DX is explained, then there isn't a strong need for it.

So, as a conclusion, with this pre-requisite in mind, it's all good from my side. Thx, bye.

pgu avatar Apr 15 '22 20:04 pgu