ui icon indicating copy to clipboard operation
ui copied to clipboard

Disabled Button state not transmitted to Link - asChild

Open MagicKitty opened this issue 2 years ago • 6 comments

Hello there, I have tried to implement this example in which I try to disable the button. Seems not working. The variant link added to the button isn't working either:

<Button asChild disabled={!isValid}>
  <Link to={'/final'}>
    Groot
  </Link>
</Button>

Is there a way to disable a Link?

Have a nice day! Great work! Nico

MagicKitty avatar Nov 05 '23 09:11 MagicKitty

@MagicKitty having a similar issue, did you find a fix?

abheektripathy avatar Nov 05 '23 17:11 abheektripathy

Same here

MrLightful avatar Nov 05 '23 22:11 MrLightful

try,
<Button asChild> <Link href='/final' target="_blank"> Groot </Link> </Button>

Josevictor2 avatar Nov 05 '23 23:11 Josevictor2

As I understand the problem: When you declare a button with asChild, what happens is the Button is never rendered in the DOM. So basically no tailwindcss classes can be passed from Button to Link with group. I cannot really figure out a good solution. The only thing that can be OKish is to style Link as a button:

const disabled = false;
<Link
  to={'/'}
  className={cn(
    buttonVariants({ variant: 'outline' }),
    disabled && 'pointer-events-none opacity-50',
  )}
>
  Click here
</Link>

MagicKitty avatar Nov 06 '23 10:11 MagicKitty

Only using shadcn for 20 min and already disapointed :/

jeromemeichelbeck avatar Jan 28 '24 17:01 jeromemeichelbeck

Only using shadcn for 20 min and already disapointed :/

I'm starting to feel the same way. Will switch to TailwindUI Catalyst when they have a Vue version available

titusdecali avatar Feb 03 '24 01:02 titusdecali

This issue has been automatically closed because it received no activity for a while. If you think it was closed by accident, please leave a comment. Thank you.

shadcn avatar Feb 25 '24 23:02 shadcn

When you are using asChild, the Button component simply passes props to the child component, in our case, to the Link component.

I am using @tanstack/router, so when the disabled prop is passed to the Link component, @tanstack/router sets the aria-disabled attribute.

Shadcn creates buttonVariants for you (this is the code from Shadcn WITHOUT SOLUTION):"

import { cva } from "class-variance-authority";

export const buttonVariants = cva(
  "tw-inline-flex tw-items-center tw-justify-center tw-whitespace-nowrap tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50",
  {
    variants: {
      variant: {
        outline: "tw-border tw-border-input tw-bg-background hover:tw-bg-accent hover:tw-text-accent-foreground",
        destructive: "tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/90",
        secondary: "tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80",
        default: "tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90",
        link: "tw-text-primary tw-underline-offset-4 hover:tw-underline",
        ghost: "hover:tw-bg-accent hover:tw-text-accent-foreground",
      },
      size: {
        lg: "tw-h-11 tw-rounded-md tw-px-8",
        default: "tw-h-10 tw-px-4 tw-py-2",
        sm: "tw-h-9 tw-rounded-md tw-px-3",
        icon: "tw-h-10 tw-w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

I added these classes to the end of the first line of cva, and it helped

cva(
"...aria-disabled:tw-opacity-50 aria-disabled:tw-pointer-events-none",
{
  variants: ...

And this will work:

import { Button } from "@/libs/shadcn/ui/button";
import { Link } from "@tanstack/react-router";

<Button variant="ghost" disabled={true} asChild>
  <Link to="/interviews/$interviewId" params={{ interviewId }}>
    Some link
  </Link>
</Button>;

nikitaprokopov avatar Apr 27 '24 02:04 nikitaprokopov

Hello there, I have tried to implement this example in which I try to disable the button. Seems not working. The variant link added to the button isn't working either:

<Button asChild disabled={!isValid}>
  <Link to={'/final'}>
    Groot
  </Link>
</Button>

Is there a way to disable a Link?

Have a nice day! Great work! Nico

My solution for this case is to set asChild to be the ! of whatever the disabled value is.

For example, in your case

<Button asChild={isValid} disabled={!isValid}>
  <Link to={'/final'}>
    Groot
  </Link>
</Button>

colinleefish avatar May 02 '24 09:05 colinleefish

I settled on this approach which is nice enough:

  if (disabled) {
    return (
      <Button variant="link" className={className} disabled>
        {children}
        <ExternalLinkIcon size={"0.8em"} className="ml-1" />
      </Button>
    );
  }
  return (
    <Button variant="link" asChild className={className}>
      <Link href={href} target="_blank">
        {children}
        <ExternalLinkIcon size={"0.8em"} className="ml-1" />
      </Link>
    </Button>
  );

Respects the idea that a disabled link isn't really a link at all!

zakandrewking avatar May 14 '24 04:05 zakandrewking

When you are using asChild, the Button component simply passes props to the child component, in our case, to the Link component.

I am using @tanstack/router, so when the disabled prop is passed to the Link component, @tanstack/router sets the aria-disabled attribute.

Shadcn creates buttonVariants for you (this is the code from Shadcn WITHOUT SOLUTION):"

import { cva } from "class-variance-authority";

export const buttonVariants = cva(
  "tw-inline-flex tw-items-center tw-justify-center tw-whitespace-nowrap tw-rounded-md tw-text-sm tw-font-medium tw-ring-offset-background tw-transition-colors focus-visible:tw-outline-none focus-visible:tw-ring-2 focus-visible:tw-ring-ring focus-visible:tw-ring-offset-2 disabled:tw-pointer-events-none disabled:tw-opacity-50",
  {
    variants: {
      variant: {
        outline: "tw-border tw-border-input tw-bg-background hover:tw-bg-accent hover:tw-text-accent-foreground",
        destructive: "tw-bg-destructive tw-text-destructive-foreground hover:tw-bg-destructive/90",
        secondary: "tw-bg-secondary tw-text-secondary-foreground hover:tw-bg-secondary/80",
        default: "tw-bg-primary tw-text-primary-foreground hover:tw-bg-primary/90",
        link: "tw-text-primary tw-underline-offset-4 hover:tw-underline",
        ghost: "hover:tw-bg-accent hover:tw-text-accent-foreground",
      },
      size: {
        lg: "tw-h-11 tw-rounded-md tw-px-8",
        default: "tw-h-10 tw-px-4 tw-py-2",
        sm: "tw-h-9 tw-rounded-md tw-px-3",
        icon: "tw-h-10 tw-w-10",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  },
);

I added these classes to the end of the first line of cva, and it helped

cva(
"...aria-disabled:tw-opacity-50 aria-disabled:tw-pointer-events-none",
{
  variants: ...

And this will work:

import { Button } from "@/libs/shadcn/ui/button";
import { Link } from "@tanstack/react-router";

<Button variant="ghost" disabled={true} asChild>
  <Link to="/interviews/$interviewId" params={{ interviewId }}>
    Some link
  </Link>
</Button>;

This works perfectly fine. Just remember that if your Link component doesn't set the aria-disabled attribute you will have to do it yourself. In my case i was using Inertia so my PaginationLink (a button under the hood) got change to this

//From this
<PaginationLink
    ...
    disabled={previousLink.url === null}
    asChild>
        <Link
            ...
        >
        </Link>
</PaginationLink>

//To this
<PaginationLink
        ...
        asChild
>
    <Link aria-disabled={previousLink.url === null}
    ...
    >
        ...
    </Link>
</PaginationLink>

Prop disabled on the button doesn't do anything.

Other thing to keep in mind is to change "tw-" for the tailwind prefix your using.

sntlln93 avatar May 23 '24 04:05 sntlln93

I came up with this, which basically always renders a (disabled) button when the disabled attribute is set:

- const Comp = asChild ? Slot : 'button';
+ const Comp = asChild && !disabled ? Slot : 'button';

soulchild avatar Jul 11 '24 08:07 soulchild