20

If I want a button but, only the presentational part of that, so if I do:

import styled from 'styled-components'

const Button = styled.button`
  color: red;
  text-align: center;
`

I'm forced to render a button tag, but what about if semantically I need an anchor?

Raphael Rafatpanah
  • 19,082
  • 25
  • 92
  • 158
cl0udw4lk3r
  • 2,663
  • 5
  • 26
  • 46

6 Answers6

33

Use the "as" polymorphic prop in v4

copy/pasta from the example in the docs:

const Component = styled.div`
  color: red;
`;

render(
  <Component
    as="button"
    onClick={() => alert('It works!')}
  >
    Hello World!
  </Component>
)
Beau Smith
  • 33,433
  • 13
  • 94
  • 101
11

styled-components provides withComponent that'll be useful for cases where you want to use an a different tag with a component. This is similar to @siddharthkp's answer in function, but uses the API.

Example from the documentation:

const Button = styled.button`
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

// We're replacing the <button> tag with an <a> tag, but reuse all the same styles
const Link = Button.withComponent('a')

// Use .withComponent together with .extend to both change the tag and use additional styles
const TomatoLink = Link.extend`
  color: tomato;
  border-color: tomato;
`;

render(
  <div>
    <Button>Normal Button</Button>
    <Link>Normal Link</Link>
    <TomatoLink>Tomato Link</TomatoLink>
  </div>
);
typeoneerror
  • 55,990
  • 32
  • 132
  • 223
7

You can use it with a anchor tag as well, there's nothing stopping you.

import styled from 'styled-components'

const Button = styled.a`
  color: red;
  text-align: center;
`

If you want to keep both, you can reuse the styles by pulling them out:

import styled from 'styled-components'

const styles = `
  color: red;
  text-align: center;
`

const Button = styled.button`
  ${styles}
`

const LinkButton = styled.a`
  ${styles}
`
siddharthkp
  • 71
  • 2
  • 6
  • 1
    Yes, but why should I create a brand new "style focused" component only for specify a different *semantic* tag? – cl0udw4lk3r Apr 22 '17 at 08:21
  • Whoops, sorry for the super late reply. If you don't like the API for doing this, @typeoneerror's answer talks about the `withComponent` which can be useful: https://stackoverflow.com/a/47982898/1501871 – siddharthkp Jan 18 '18 at 11:53
  • If you're question is more about the library decision that styled-components folks made, they wanted to make it a convention to enforce the practice of separating `style` from `logic`. A good way to do that is to create a new _style_ component every time. @mxstbr talks about it a little more here: https://www.youtube.com/watch?v=bIK2NwoK9xk – siddharthkp Jan 18 '18 at 11:57
6

I've asked the same question on styled-components issue tracker: https://github.com/styled-components/styled-components/issues/494

And the current "solution" that I've found is:

// agnosticStyled.js
import React from 'react'
import styled from 'styled-components'

export default styled(
  ({tag = 'div', children, ...props}) =>
    React.createElement(tag, props, children)
)

And then when you need it:

import React from 'react'
import styled from './agnosticStyled'

 const Button = styled`
   color: palevioletred;
   text-transform: uppercase;
 `

 export default Button

And finally:

import React from 'react'
import Button from './Button'

const Component = () => 
  <div>
    <Button>button</Button>
    <Button tag="button">button</Button>
    <Button tag="a" href="https://google.com">button</Button>
   </div>

export default Component 

Here a full functioning example: https://codesandbox.io/s/6881pjMLQ

cl0udw4lk3r
  • 2,663
  • 5
  • 26
  • 46
  • Using this approach, how do you get the ref of the element? – cusX Jul 20 '17 at 06:58
  • Dunno, I will experiment soon! What's your use case, in particular? – cl0udw4lk3r Jul 20 '17 at 12:09
  • Alright let me know. For my use case, I'm creating a button where it can be a `Link` button, or an anchor button or a "button" button. Then, I would need to grab the "ref" of the button, so that I can use it as an anchor element for my dialog to pop up from. Basically for animation. – cusX Jul 22 '17 at 06:55
3

Since we're just using JavaScript, why not use a function?

const myButtonStyle = (styled, tag) => {
  return styled[tag]`
    color: red;
    text-align: center;
  `
}

const Button = myButtonStyle(styled, 'button')
Raphael Rafatpanah
  • 19,082
  • 25
  • 92
  • 158
  • 1
    Because this way I'm still forced to do: `const Button = myButtonStyle(styled, 'button')` and `const LinkButton = myButtonStyle(styled, 'a')` when from a **presentation** point of view they are the same thing! – cl0udw4lk3r Apr 18 '17 at 14:29
3

As @typeoneerror pointed out, styled-components provides WithComponent. You can use this to to create a new component based on a prop containing the tag. Piggybacking off the example, it would look like this:

const _Button = styled.button`
  color: palevioletred;
  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

const Button = ({ as: tag = 'button', children, ...props }) => {

  // We're replacing the <button> tag with whatever tag is assigned to the 'as' prop (renamed to 'tag' and defaulted to button), but reuse all the same styles
  
  const Composed = _Button.withComponent(tag);
  
  // We return the newly-created component with all its props and children
  
  return <Composed {...props}>{children}</Composed>;
};

render(
  <div>
    <Button>Normal Button</Button>
    <Button as='a'>Normal Link</Button>
  </div>
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>