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

asChild props, like in Radix UI #5321

Open
zxti opened this issue Oct 28, 2023 · 6 comments
Open

asChild props, like in Radix UI #5321

zxti opened this issue Oct 28, 2023 · 6 comments
Labels

Comments

@zxti
Copy link

zxti commented Oct 28, 2023

Provide a general summary of the feature here

Radix UI supports the asChild prop, which lets you provide a complete other component to serve in a certain role.

This would let you much more easily adopt React Aria Components within codebases that already have other components (but also some gotchas that users just have to be aware of). Especially for things like Buttons, etc.

🤔 Expected Behavior?

See the Radix docs: https://www.radix-ui.com/primitives/docs/guides/composition

😯 Current Behavior

You are limited to just using React Aria Components with itself.

💁 Possible Solution

No response

🔦 Context

Has been brought up as feedback in the past:

#2331 (comment)

https://news.ycombinator.com/item?id=35853692

💻 Examples

No response

🧢 Your Company/Team

No response

🕷 Tracking Issue

No response

@devongovett
Copy link
Member

devongovett commented Oct 28, 2023

I'd recommend reading through our Advanced customization guide, in particular the section about consuming contexts. All of the contexts we provide are exported, so you can consume them in your own components. This makes it possible to reuse existing components you may have, either by modifying them to consume those contexts internally or by creating a small wrapper.

For buttons in particular, we do rely on the normalization done by React Aria hooks internally, so when a component contains a button it expects that this is happening. Therefore using an arbitrary element would not be enough, it would need to include the hooks too. But, if you want to customize the element you can drop down to the hook API yourself, and as long as it consumes from ButtonContext it will work with other React Aria components. See this example from the button docs.

We've discussed APIs like as and asChild before, but we think they are too blunt an instrument. It's extremely powerful, but it's too easy to break the accessibility, behavior, interactions, or types of a component, and supporting this would prevent us from being able to make internal changes in the future. Usually there is a better way to solve individual problems that might have been solved by these tools in the past, such as sharing styles (reuse the same CSS class names), behavior, etc. But if there's something in particular you've come across that isn't already possible to solve please let us know.

@devongovett devongovett added the RAC label Nov 8, 2023
@aspirisen
Copy link

I think it is worth to add asChild prop. It is not necessary when designs are matching adobe's one. But react area components are supposed to be applied to various scenarios, even to those which were not built in into the library. Sometimes it is much easier and more flexible to use different component, but with the same functionality.

I've used as prop in styled-components for a while, and find it less usable rather than asChild. With asChild you do not need to implement complex type inference, the props types are still simple as before. Also, with asChild you can combine behavior of several components into one.

@devongovett
Copy link
Member

devongovett commented Mar 30, 2024

With asChild you do not need to implement complex type inference

asChild is not type safe, that's why it seems simpler. You can put any component in there even if it doesn't accept the right props or render the right element and it'll just be broken at runtime instead.

What are you trying to achieve? There's usually another way to do it. Happy to help if you have a specific case. Make sure to read the guide linked above as well.

@aspirisen
Copy link

@devongovett

asChild is not type safe, that's why it seems simpler. You can put any component in there even if it doesn't accept the right props or render the right element and it'll just be broken at runtime instead.

I feel that asChild has the same level of type safety as as prop. In the component in children of asChild typescript still performs type checking as usual. Also, asChild work better with generic components, if you use as prop there can be difficulties with correct type inference of generic components in as prop. And as prop requires more complex typescript computations - I noticed that when I moved to asChild approach autocompletion has become much faster

What are you trying to achieve? There's usually another way to do it. Happy to help if you have a specific case. Make sure to read the guide linked above as well.

Mostly for styling, I have several components that covers specific area of css - Flex, Grid, Stack, Paint, Text etc. And then combine styles that I need by using asChild. I am just trying to avoid using all-in-one Box component, the same as giving control to all css properties to all components.

@devongovett
Copy link
Member

I feel that asChild has the same level of type safety as as prop.

In TypeScript you can't really specify the types of React children. Here's an example. As you can see, Parent should only accept children with a foo prop that is a string, but TypeScript actually allows any child with a different type, or even a random div that doesn't accept a foo prop at all. That is why I say children are untyped, so therefore asChild is untyped. Accepting arbitrary children can cause runtime errors, broken behavior, accessibility issues, and other problems.

Mostly for styling, I have several components that covers specific area of css - Flex, Grid, Stack, Paint, Text etc.

I think another approach to this is to share the styles rather than components. That way they can be reused across different components. This example uses inline styles but you could use tailwind, css-in-js, etc. to achieve the same result.

function flex(options) {
  return {display: 'flex', gap: options.gap, flexDirection: options.direction, /* ... */ };
}

<ReactAriaComponent style={flex({...})} />

This approach is more granular - you are explicit about what is being shared (only styles, no events, or other hidden behaviors), and you have control over how multiple of these are merged together (with asChild you don't have control over how props from multiple components are merged). This also makes it easier to share styles between different raw DOM elements too, rather than being locked into a <div> being rendered by <Flex> or needing an as/asChild prop there too.

Another way to think about this is that components should be responsible for the behavior, semantics, and elements that are rendered, and styling should be passed into them. The component knows what is represents – a button, checkbox, switch, etc. – so it needs to control the rendered elements and behaviors. asChild flips this around so behaviors get passed down to arbitrary elements (which may also have other conflicting behaviors of their own), easily resulting in unexpected issues and bugs. Passing styles into components avoids these issues and results in more predictable behavior and fewer potential accessibility issues.

@aspirisen
Copy link

@devongovett thanks for detailed explanation.

I wanted to emphasize that asChild prop is better than as prop approach in my opinion. But, yes there is no way to type react children. I just think that asChild can be used for composition because of server components support.

Also about passing functions to style components, I thought I tried this approach, but after that all my JSX turned into divs and spans what was harder to read than named jsx tags like Flex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants