-
-
Notifications
You must be signed in to change notification settings - Fork 32.8k
[Button] Add support for anchor tag attributes in Button component #45765
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
[Button] Add support for anchor tag attributes in Button component #45765
Conversation
Netlify deploy previewhttps://deploy-preview-45765--material-ui.netlify.app/ Bundle size report |
834c953 to
d94b7ab
Compare
docs/src/pages/premium-themes/onepirate/modules/components/Button.js
Outdated
Show resolved
Hide resolved
1f121b8 to
cf030c6
Compare
9b5b2b0 to
7b4576a
Compare
- Updated ButtonProps to allow anchor tag attributes (href, target, rel, download) when Button acts as an anchor. - Added type constraints to ensure correct type inference for anchor tag props. - Fixed TypeScript type errors related to usage of target and download without href. - Updated tests to verify anchor tag behavior and type validation.
- Ran `pnpm docs:typescript:formatted` to automatically fix import order and formatting.
- Restore original import order as per project conventions
- Combined base button props with separate types for anchor and non-anchor buttons. - Removed the need for ConstrainedButtonProps by using union types to handle href-specific attributes. - Ensured proper type restrictions for buttons with and without href.
41d51fa to
e92ec49
Compare
|
|
||
| export type ButtonProps< | ||
| // Anchor-specific attributes that should only be available with href | ||
| type AnchorAttributes = 'target' | 'rel' | 'download' | 'hrefLang' | 'ping' | 'referrerPolicy'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this all of anchor attributes? How do we ensure that this is enough 🤔.
To be honest, I don't lean on this solution that much. I would update the docs on how to apply these props to the Button instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're absolutely right about the anchor attributes—creating an explicit list does risk missing some attributes or falling out of sync with future HTML specs. I really appreciate your suggestion to focus on documentation instead.
As an alternative approach that doesn’t require listing specific attributes, we could document this pattern:
import { Button, ButtonProps } from '@mui/material';
// This correctly adds all anchor props only when href is present
type CustomButtonProps<C extends React.ElementType = 'button'> = ButtonProps<C, {}> &
(({ href: string } & React.ComponentProps<'a'>) | { href: never });
export const CustomButton = (props: CustomButtonProps) => {
return <Button {...props} />;
};This approach leverages TypeScript's built-in types to automatically include all anchor attributes, helping us stay aligned with future HTML specs while minimizing maintenance overhead.
I hadn’t considered that earlier, but it’s a great idea, and I’m definitely on board with it. Would it make sense to close my PR and open a new one that addresses issue #45729 by updating the documentation instead of modifying the type definitions?
@siriwatknp @ZeeshanTamboli , I’d love to get your input on whether this seems like the best course of action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Before adding docs, we should first understand why the target attribute isn't supported on custom buttons that are wrapped with Material UI Buttons in TS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the issue with target not being properly recognized on custom button component lies with the ButtonProps, though I'm not entirely certain about all the details.
ButtonProps type definition:-
export type ButtonProps
RootComponent extends React.ElementType = ButtonTypeMap['defaultComponent'],
AdditionalProps = {},
> = OverrideProps<ButtonTypeMap<AdditionalProps, RootComponent>, RootComponent> & {
component?: React.ElementType;
}This definition doesn't seem to explicitly include the anchor element attributes like target when an href is provided.
What's interesting is that the ExtendButtonBase type in ButtonBase.d.ts file does seem to handle anchor transformation:
export type ExtendButtonBase<TypeMap extends OverridableTypeMap> = ((
props: { href: string } & OverrideProps<ExtendButtonBaseTypeMap<TypeMap>, 'a'>,
) => React.JSX.Element) &
OverridableComponent<ExtendButtonBaseTypeMap<TypeMap>>;From what I can observe:
- When using Button directly,
hrefandtargetwork perfectly - When wrapping Button in our custom component using
ButtonProps, TypeScript doesn't recognize anchor attributes - The component still works correctly at runtime anyway despite TypeScript errors when using
ButtonProps
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When wrapping Button in our custom component using ButtonProps, TypeScript doesn't recognize anchor attributes
Is this a TypeScript limitation? And how? If so, we should document how to make it recognize the target attribute and mention that it's a TS limitation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spent some time looking into this, and while I couldn’t find anything concrete in the TypeScript documentation or issue tracker, it seems like TypeScript might be struggling with narrowing types when they’re passed through a generic wrapper.
It seems the Button component handles href and target fine because of how the overloads are set up in ExtendButtonBase. But when using ButtonProps in a wrapper, that conditional behavior seems to get flattened or lost, and target is no longer recognized.
I’m not sure if this is due to TypeScript limitations or just a side effect of how the types are structured. If anyone has more insight, I’d be happy to learn more or help clarify this further in the docs if it turns out to be expected behavior.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I looked into it — this is a TypeScript limitation.
When you wrap Button in a custom component like CustomButton, TypeScript can't infer that target is valid when href is used.
As a workaround (though not ideal), you can explicitly extend both ButtonProps and anchor attributes:
import { Button, ButtonProps } from '@mui/material';
type AnchorButtonProps = ButtonProps & React.AnchorHTMLAttributes<HTMLAnchorElement>;
const CustomButton = (props: AnchorButtonProps) => {
return <Button {...props} />;
};
export default function App() {
return (
<CustomButton href="https://example.com" target="_blank">
My custom button
</CustomButton>
);
}That said, if the custom component isn’t adding any new logic or props, I don't see much value in wrapping Button just to forward props — so I raised that question in the issue: #45729 (comment)
|
Closing this PR since the related issue is a TS limitation. See #45765 (comment) and #45729 (comment) |
Summary:
This PR enhances the
Buttoncomponent by adding support for anchor-specific attributes likehref,target,rel, anddownloadwhen the component is rendered as an anchor tag. It ensures correct TypeScript typing for these attributes and fixes type errors whentargetanddownloadare used withouthref.Related Issue: #45729
(You can refer to the issue for more details, including the provided Stackblitz example and image.)
Reproduction:
You can test the behavior using the original Stackblitz example provided by the author here:
Stackblitz Example
Changes:
ButtonPropsto support anchor tag attributes whenhrefis provided.target,download, or other anchor-specific attributes withouthref.Buttoncomponent.Testing:
Tests have been added to verify that the
Buttoncomponent behaves correctly when used with anchor-specific attributes.Type errors are now correctly thrown when
targetordownloadis used withouthref.All related tests pass, including edge cases where
component="a"is used with anchor attributes.I have followed (at least) the PR section of the contributing guide.
I have tested the changes in this PR.