Skip to content

Make tailwind optional #543

Description

@mindplay-dk

Problem Statement

I don't use tailwind, and I don't want to introduce tailwind into a project that uses a completely different design system, just for one component.

I've seen #13 and I am already using the package without tailwind.

But why does it need to be tailwind by default?

It doesn't look like there is any real dependency on tailwind at all.

Proposed Solution

If you would drop the tailwind requirement and make it optional, we could have a smaller bundle size.

The default components for all standard HTML elements can literally just be:

({ node, children }) =>
  createElement(node.tagName, node.properties, children)

This would be totally fine for most projects - many sites will already have a default typography, and this will "just work", and will look like the rest of the content on the web site.

Ideally, I would suggest you should export two components, e.g. Streamdown for tailwind users (to retain backwards compatibility) and StreamdownUnstyled for the rest of us.

With a bit of internal refactoring, Streamdown can essentially be a wrapper around StreamdownUnstyled, such that tree-shaking can shed the tailwind overhead from the bundle.

With an internal factory function for the component, you could easily omit tailwind-merge from the unstyled component and shed this from the bundle as well.

As an side, the className property doesn't work very well, probably not even for tailwind users - setting it to hello produces a class attribute like:

space-y-4 whitespace-normal [&>*:first-child]:mt-0 [&>*:last-child]:mb-0 hello

Even tailwind users will need to undo this styling by overriding each of the properties - and the rest of us just have these unused classnames in our resulting HTML. I think it would be simpler for everyone if className would replace rather than add to the attribute. Anyhow, that's just my opinion - if there was an unstyled version of the component, this wouldn't matter to me.

Lastly, I would suggest adding a defaultComponent property - since we have the allowedTags property, which can allow tags that don't have a defined render-function in components, this would act as a fallback for those. It would also mean that the default configuration for the unstyled component can literally just be the function I showed above, with nothing configured for components by default at all.

Alternatives Considered

This is the obvious answer:

              <Streamdown
                key={index}
                className="markdown-content"
                components={{
                  h1: ({ children }) => <h1>{children}</h1>,
                  h2: ({ children }) => <h2>{children}</h2>,
                  h3: ({ node, children }) =><h3>{children}</h3>,
                  h4: ({ children }) => <h4>{children}</h4>,
                  h5: ({ children }) => <h5>{children}</h5>,
                  h6: ({ children }) => <h6>{children}</h6>,
                  p: ({ children }) => <p>{children}</p>,
                  strong: ({ children }) => <strong>{children}</strong>,
                  em: ({ children }) => <em>{children}</em>,
                  ul: ({ children }) => <ul>{children}</ul>,
                  ol: ({ children }) => <ol>{children}</ol>,
                  li: ({ children }) => <li>{children}</li>,
                  a: ({ children }) => <a>{children}</a>,
                  code: ({ children }) => <code>{children}</code>,
                  pre: ({ children }) => <pre>{children}</pre>,
                  blockquote: ({ children }) => (
                    <blockquote>{children}</blockquote>
                  ),
                  table: ({ children }) => <table>{children}</table>,
                  thead: ({ children }) => <thead>{children}</thead>,
                  tbody: ({ children }) => <tbody>{children}</tbody>,
                  tr: ({ children }) => <tr>{children}</tr>,
                  th: ({ children }) => <th>{children}</th>,
                  td: ({ children }) => <td>{children}</td>,
                  img: () => <img />,
                  hr: () => <hr />,
                  sup: ({ children }) => <sup>{children}</sup>,
                  sub: ({ children }) => <sub>{children}</sub>,
                  section: ({ children }) => <section>{children}</section>,
                }}>
                {part.text}
              </Streamdown>

It's a lot of boilerplate just to remove tailwind. (and as said, it doesn't quite work.)

And we're still shipping all the default tailwind components.

Use Case

The use case should be obvious for anything who isn't using tailwind. 😊

Priority

Important

Contribution

  • I am willing to help implement this feature

Additional Context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions