Skip to content

Instantly share code, notes, and snippets.

@adamwathan
Last active August 21, 2020 01:52
Show Gist options
  • Star 39 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save adamwathan/06faa4e9ee1571d3eb47cc0349b965cd to your computer and use it in GitHub Desktop.
Save adamwathan/06faa4e9ee1571d3eb47cc0349b965cd to your computer and use it in GitHub Desktop.

Quick update: Some folks have expressed concern that it would suck to not be able to actually test out the interactive components like clicking a button to see a dropdown transition from open to closed.

The live previews in Tailwind UI would absolutely still be living, interactive demos. It's just the code snippets that would use HTML comments, we'd still be using Alpine internally under the hood to give you a real demo and just maintaining two code snippets for each interactive component (we already do this in some sense anyways).


Integrating Tailwind UI with JavaScript

To make Tailwind UI as universal as possible, we've built all the components using HTML only.

The vast majority of components don't need JavaScript at all and are completely ready to go out of the box, but things that are interactive like dropdowns, modals, etc. require you to write some JS to make them work the way you'd expect.

In these situations we've provided some simple comments in the HTML to explain things like what classes you need use for different states (like a toggle switch being on or off), or what classes we recommend for transitioning elements on to or off of the screen (like a modal opening).

This guide explains how to take these comments and translate them into working code using some popular JS libraries.

Accessibility

We've done our best to ensure that all of the markup in Tailwind UI is as accessible as possible, but when you're building interactive components, many accessibility best practices can only be implemented with JavaScript.

For example:

  • Making sure components are properly keyboard accessible (dropdowns should be navigated with up/down arrow keys, modals should close when you press escape, tabs should be selected using the left/right arrow keys, etc.)
  • Correctly handling focus (you shouldn't be able to tab to an element behind a modal, the first item in a dropdown should be auto-focused when the dropdown opens, etc.)
  • Synchronizing ARIA attributes with component state (adding aria-expanded="true" when a dropdown is open, setting aria-checked to true when a toggle is on, updating aria-activedescendant when navigating the options in an autocomplete, etc.)
  • ...and many other concerns.

Because the components in Tailwind UI are HTML-only, it is up to you to follow accessibility best practices adding interactive behavior with JavaScript.

To learn more about building accessible UI components, we recommend studying the WAI-ARIA Authoring Practices published by the W3C.

React

The first thing you'll need to do to incorporate any Tailwind UI component into a React project is convert the HTML to JSX. Usually this just means converting class to className, but for components that include inline SVG elements you'll also need to camelize any dash-case SVG attributes like fill-rule, stroke-width, and others.

- <button type="button" class="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150">
+ <button type="button" className="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150">
-   <svg class="-ml-1 mr-2 h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
+   <svg className="-ml-1 mr-2 h-5 w-5" fill="currentColor" viewBox="0 0 20 20">
-     <path fill-rule="evenodd" clip-rule="evenodd" d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884zM18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
+     <path fillRule="evenodd" clipRule="evenodd" d="M2.003 5.884L10 9.882l7.997-3.998A2 2 0 0016 4H4a2 2 0 00-1.997 1.884zM18 8.118l-8 4-8-4V14a2 2 0 002 2h12a2 2 0 002-2V8.118z"/>
    </svg>
    Button text
  </button>

Dynamic classes

When an element needs different classes applied based on some state (like a toggle being on or off), we list the classes for each state in a comment directly above the element:

<!-- Simple toggle switch -->
<!-- Off: bg-gray-200 -->
<!-- On: bg-indigo-600 -->
<span aria-checked="false" class="relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline" role="checkbox" tabindex="0">
  <!-- Off: translate-x-0 -->
  <!-- On: translate-x-5 -->
  <span aria-hidden="true" class="inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span>
</span>

To make this work in React, conditionally include the necessary classes on each element by checking a prop or piece of state:

import { useState } from 'react'

function SimpleToggle() {
  const [isOn, setIsOn] = useState(false)
  return (
    <span
      onClick={() => setIsOn(!isOn)}
      aria-checked={isOn}
      class={`${isOn ? 'bg-indigo-600' : 'bg-gray-200'} relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline`} role="checkbox" tabindex="0"
    >
      <span
        aria-hidden="true"
        class={`${isOn ? 'translate-x-5' : 'translate-x-0'} inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200`}
      ></span>
    </span>
  )
}

Enter/Leave transitions

For elements that should be dynamically shown or hidden (like the panel on a dropdown), we include the recommended transition styles in a comment directly above the dynamic element:

<div class="relative ...">
  <button type="button" class="...">
    Options
  </button>

  <!-- Show/hide this element based on the dropdown state -->
  <!-- Entering: transition ease-out duration-100 transform opacity-[0-100] scale-[95-100] -->
  <!-- Closing: transition ease-in duration-75 transform opacity-[100-0] scale-[100-95] -->
  <div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
    <div class="rounded-md bg-white shadow-xs">
      <!-- Snipped  -->
    </div>
  </div>
</div>

You'll notice the class lists often include entries like opacity-[0-100] — all this means is that the element should transition from opacity-0 to opacity-100 during that phase of the transition.

React doesn't include any first-party transition components out of the box, but we've put together a very simple wrapper around react-transition-group that makes it easy to implement our transitions in your project.

To get started, grab the code for our transition component on GitHub and save it alongside your other React components (we've called it Transition.js for our examples).

We'll likely publish the component to npm eventually, but for now you should just include the code in your project manually.

To implement the transition, wrap the dynamic element in the Transition component, passing through the following props:

  • show, whether or not the dynamic element should be showing
  • enter, a list of classes that should be present during the entire enter phase
  • enterFrom, a list of classes that represent the element's state at the beginning of the enter phase
  • enterTo, a list of classes that represent the element's state at the end of the enter phase
  • leave, a list of classes that should be present during the entire leave phase
  • leaveFrom, a list of classes that represent the element's state at the beginning of the leave phase
  • leaveTo, a list of classes that represent the element's state at the end of the leave phase

Here's what that looks like when applied to the dropdown example from above:

import { useState } from 'react'
import Transition from './Transition.js'

function Dropdown() {
  const [isOpen, setIsOpen] = useState(false)
  return (
    <div className="relative ...">
      <button type="button" onClick={() => setIsOpen(!isOpen)} className="...">
        Options
      </button>

      <Transition
        show={isOpen}
        enter="transition ease-out duration-100 transform"
        enterFrom="opacity-0 scale-95"
        enterTo="opacity-100 scale-100"
        leave="transition ease-in duration-75 transform"
        leaveFrom="opacity-100 scale-100"
        leaveTo="opacity-0 scale-95"
      >
        <div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
          <div className="rounded-md bg-white shadow-xs">
            {/* Snipped */}
          </div>
        </div>
      </Transition>
    </div>
  )
}

Vue.js

Since Vue templates are no different than regular HTML, the first step to pulling a Tailwind UI component into a Vue project is to just paste the code inside a <template> tag of a single file Vue component.

Dynamic classes

When an element needs different classes applied based on some state (like a toggle being on or off), we list the classes for each state in a comment directly above the element:

<!-- Simple toggle switch -->
<!-- Off: bg-gray-200 -->
<!-- On: bg-indigo-600 -->
<span aria-checked="false" class="relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline" role="checkbox" tabindex="0">
  <!-- Off: translate-x-0 -->
  <!-- On: translate-x-5 -->
  <span aria-hidden="true" class="inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span>
</span>

To make this work in Vue, conditionally include the necessary classes on each element by checking a prop or piece of state:

<template>
  <span
    @click="isOn = !isOn"
    :aria-checked="isOn"
    :class="isOn ? 'bg-indigo-600' : 'bg-gray-200'"
    class="relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline"
    role="checkbox"
    tabindex="0"
  >
    <span
      aria-hidden="true"
      :class="isOn ? 'translate-x-5' : 'translate-x-0'"
      class="inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"
    ></span>
  </span>
</template>

<script>
  export default {
    data: () => ({
      isOn: false,
    })
  }
</script>

Enter/Leave transitions

For elements that should be dynamically shown or hidden (like the panel on a dropdown), we include the recommended transition styles in a comment directly above the dynamic element:

<div class="relative ...">
  <button type="button" class="...">
    Options
  </button>

  <!-- Show/hide this element based on the dropdown state -->
  <!-- Entering: transition ease-out duration-100 transform opacity-[0-100] scale-[95-100] -->
  <!-- Closing: transition ease-in duration-75 transform opacity-[100-0] scale-[100-95] -->
  <div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
    <div class="rounded-md bg-white shadow-xs">
      <!-- Snipped  -->
    </div>
  </div>
</div>

You'll notice the class lists often include entries like opacity-[0-100] — all this means is that the element should transition from opacity-0 to opacity-100 during that phase of the transition.

To implement transitions like this in Vue, use Vue's built-in <transition> component and pass the necessary transition styles through the custom transition class props:

<template>
  <div class="relative ...">
    <button type="button" @click="isOpen = !isOpen" class="...">
      Options
    </button>

    <transition
      enter-active-class="transition ease-out duration-100 transform"
      enter-class="opacity-0 scale-95"
      enter-to-class="opacity-100 scale-100"
      leave-active-class="transition ease-in duration-75 transform"
      leave-class="opacity-100 scale-100"
      leave-to-class="opacity-0 scale-95"
    >
      <div v-show="isOpen" class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
        <div class="rounded-md bg-white shadow-xs">
          <!-- Snipped  -->
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
  export default {
    data: () => ({
      isOpen: false,
    })
  }
</script>

Alpine.js

Alpine.js is a fairly new, heavily Vue-inspired library designed to make it easy to add interactive behavior to traditional server-rendered websites, using a light-weight declarative syntax directly in your HTML. It's what we use for the Tailwind UI site itself, and is without a doubt what I'd recommend if you'd otherwise be writing jQuery or vanilla JS.

Dynamic classes

When an element needs different classes applied based on some state (like a toggle being on or off), we list the classes for each state in a comment directly above the element:

<!-- Simple toggle switch -->
<!-- Off: bg-gray-200 -->
<!-- On: bg-indigo-600 -->
<span aria-checked="false" class="relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline" role="checkbox" tabindex="0">
  <!-- Off: translate-x-0 -->
  <!-- On: translate-x-5 -->
  <span aria-hidden="true" class="inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"></span>
</span>

To make this work in Alpine, conditionally include the necessary classes on each element based on some state you've declared in x-data:

<span
  x-data="{ isOn: false }"
  @click="isOn = !isOn"
  :aria-checked="isOn"
  :class="{'bg-indigo-600': isOn, 'bg-gray-200': !isOn }"
  class="relative inline-block flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:shadow-outline"
  role="checkbox"
  tabindex="0"
>
  <span
    aria-hidden="true"
    :class="{'translate-x-5': isOn, 'translate-x-0': !isOn }"
    class="inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"
  ></span>
</span>

Enter/Leave transitions

For elements that should be dynamically shown or hidden (like the panel on a dropdown), we include the recommended transition styles in a comment directly above the dynamic element:

<div class="relative ...">
  <button type="button" class="...">
    Options
  </button>

  <!-- Show/hide this element based on the dropdown state -->
  <!-- Entering: transition ease-out duration-100 transform opacity-[0-100] scale-[95-100] -->
  <!-- Closing: transition ease-in duration-75 transform opacity-[100-0] scale-[100-95] -->
  <div class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg">
    <div class="rounded-md bg-white shadow-xs">
      <!-- Snipped  -->
    </div>
  </div>
</div>

You'll notice the class lists often include entries like opacity-[0-100] — all this means is that the element should transition from opacity-0 to opacity-100 during that phase of the transition.

To implement transitions like this in Alpine, use the x-transition directives:

<div x-data="{ isOpen: false }" class="relative ...">
  <button type="button" @click="isOpen = !isOpen" class="...">
    Options
  </button>

  <div
    x-show="isOpen"
    x-transition:enter="transition ease-out duration-100 transform"
    x-transition:enter-start="opacity-0 scale-95"
    x-transition:enter-end="opacity-100 scale-100"
    x-transition:leave="transition ease-in duration-75 transform"
    x-transition:leave-start="opacity-100 scale-100"
    x-transition:leave-end="opacity-0 scale-95"
    class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg"
  >
    <div class="rounded-md bg-white shadow-xs">
      <!-- Snipped  -->
    </div>
  </div>
</div>
@epixian
Copy link

epixian commented Apr 10, 2020

@Merott
Copy link

Merott commented Apr 10, 2020

First of all, amazing work! 😊

I find the comments much harder to a) navigate, b) understand, and c) implement than actual working examples using Alpine.js

At least when it comes to translating the Alpine.js implementation to Vue.js, it's mostly a matter of finding instances of x-* in the code, and replacing them with the Vue alternatives. Transitions are a little bit more involved, but even they're quite easy to deal with by wrapping the element in a transition element and mapping the attributes.

I realise not every framework is going to be that straightforward but I can't imagine it being easier with documentation in comments.

The other nice thing with the Alpine.js method is that it's a fully working example that we can run and experiment with. And, it's live documentation for the components' intended usage. Static documentation can be hard to maintain.

@eriknyk
Copy link

eriknyk commented Apr 10, 2020

Great work, as a suggestion, if you added alpine examples that has 6k starts on github, you should consider add the examples for svelte (https://github.com/sveltejs/svelte) that has 32.5k starts on github.

Best Regards.

@adamwathan
Copy link
Author

@Merott thanks for your feedback!

The other nice thing with the Alpine.js method is that it's a fully working example that we can run and experiment with. And, it's live documentation for the components' intended usage. Static documentation can be hard to maintain.

This is actually what I'm kind of worried about by providing Alpine examples. The Alpine examples we have now are very incomplete and not production-ready. They are missing lots and lots of behavior you need to add to make them fully accessible, and I worry that by providing them we are encouraging people to build inaccessible websites because they mistakenly believe the examples are bullet-proof and are using them in their own projects without adding keyboard navigation, focus trapping, etc.

I'd love to come up with a way to give people copy & paste snippets that are closer to the syntax of their framework of choice, but I don't know how I can possibly do that without also misleading people to believe they are bullet-proof production-ready JS implementations, and actually providing that is just way out of scope for what we're working on right now.

@mikebronner
Copy link

I have found it immensely valuable to see a component "working as originally intended ". As such the Alpine inclusion makes sense. Or would you consider making the preview on the site still use Alpine and have a comment-only version specifically for copying only? That sounds like it might create a LOT of extra maintenance, if you couldn't automated the process of creating the comment-only version.

Love the thought you have put into the entire project, as well as how to address this issue. I like how it currently is, as I would touch every like of the copied code anyway when implementing it.

@adamwathan
Copy link
Author

Thanks @eriknyk!

Great work, as a suggestion, if you added alpine examples that has 6k starts on github, you should consider add the examples for svelte (https://github.com/sveltejs/svelte) that has 32.5k starts on github.

I added Alpine only because that's what we've been using up until now anyways. Would be open to adding more but I just whipped these together in a couple hours today and personally have never used Svelte. Agree it would be a good one to add, and Angular as well.

@adamwathan
Copy link
Author

adamwathan commented Apr 10, 2020

Or would you consider making the preview on the site still use Alpine and have a comment-only version specifically for copying only?

@mikebronner Yeah this is exactly what we'd do, the preview would still be interactive, it's just the code snippet that would be static.

@antonmyrberg
Copy link

I think this is a great approach and looking at the React example, it's exactly what I've been wanting. It's been messy removing both Alpine and kebab-case SVG attributes to just be able to render components. (Especially since ES Lint flips out when pasting the Alpine specific decorators into a JSX render).

Using something like your custom Transitions.js seem like a great middle ground.

@MrSunshyne
Copy link

Thanks for taking the time to address this. I use Vue.
Removing the alpine part from the code snippets wasn't that hard, but it was a long process compared to what tailwind is aiming at, i.e the component is built and ready to go! but now i have to spend 5minutes clean this up. I know 5minutes isn't a whole lot, but it kinda breaks the flow of being able to "compose" a page by putting(pasting really) different components.

The best option for me would be of course, just provide a version of each interactive component for Vue(and other popular libraries), but not sure that's practical. So in that sense, I welcome the html comments and I think this would be an improvement over the current way of shipping with alpine by default. Again, I clarify, adding vue is not that tedious, it's the 'removing alpine' part that mostly breaks my flow.

Thanks for the amazing work ! <3

@prenaux
Copy link

prenaux commented Apr 11, 2020

The comments are much harder/impossible for ppl that are not coders and just a lot more work. With the alpine sample a designer can copy-paste the snippets and do a decent almost functional job, which is great for quick prototypes / better wireframing.

Please keep them as an option ‘Preview|HTML Alpine|HTML w/ comments‘ in the tabs.

@clintjansen
Copy link

I was trying to implement my own transitions, but had to dive into the alpine docs to figure out what was happening.
So definitely not that clear at first sight what alpine syntax exactly does. (Coming from react dev).

Glad I found your Transitions.js file shortly after. Gets the job done perfectly.

So yea, I'm more down with comments as we have to do convert to JSX already and stripping alpine syntax from the blocks is more labor added to it.

Anyhow, great work on this!

@pryley
Copy link

pryley commented Apr 11, 2020

I’m fine with stripping the javascript from the code and replacing them with HTML comments.

At first I hated the idea...until I realised I could simply use the web inspector on the live examples and copy the DOM from the page to get the Alpine-sprinkled HTML.

@lucianghinda
Copy link

lucianghinda commented Apr 11, 2020

@adamwathan great work with this document! It is already a good example of how to transform Alpine examples into React or Vue.

As I see it, the decision seems to be between:

A. Providing the common code that everyone can use with their own JS Framework, like in the example you provided by using HTML comments

B. Providing an working example that can be directly copy/pasted for rapid prototyping (like you are doing now with alpine)

C. Providing version A and adding working examples for multiple JS frameworks (Vue, React, Svelte, ....)

Each option has pros and cons and moving from A to C also includes a lot of maintenance from your side.

I would like to provide another option D:

  • What about using the community of people who are already using Tailwind UI and invite them to upload their code with JS for each component?

I am thinking adding in Tailwind UI website for each component a tab named maybe "See community examples" where I - as a User - can either browse through other people code or upload my own. Or maybe add a comment section where we can link code samples.

Now things that I am not sure for this option are things like:

  • Should you moderate the uploaded examples? Or let us - the users - decide which works or note (maybe with some voting ...)
  • How to upload the code? (Should it be a single file with everything needed to run, or just the example for a framework without any include/require)
  • Should the code run on TailwindUI to preview it or just be available for downloa
  • What would make a User want to upload a preview of their code (here I hope the answer could be we want to grow together and help each other)

In any case I think if you decide to allow users to upload their own examples maybe there is a change to start a community of Tailwind UI users where we can support each other to develop more rapid and easier web products.

@tomhermans
Copy link

Just copy/paste and fiddle with it is the best way imho. And I'm getting to know Alpine JS while doing it. For people who don't like removing some Alpine attributes, don't they fiddle with the rest of the code? Are they just copy pasting and basically duplicating the tailwindui site for their own.. ? Seems weird

@smozely
Copy link

smozely commented Apr 12, 2020

I'm a big 👍 for the HTML comments version, converting to react has been a bit of a PITA from Alpine (time consuming not hard)

... but I really like your approach of providing HTML for the components, rather than trying to ship pre-built components in a JS framework ... I've made changes to every component I've used so far, so the flexibility of seeing what is component is built from, and then deciding how I want to structure components is a good thing!

If you had tried to ship something like React components, then I'm sure you wouldn't / couldn't have provided for the changes I needed to make, and then it would have been the same as trying to use bootstrap.

@aloukissas
Copy link

This is very helpful for us React folks. Minor nit: some of the code examples still use class= instead of className=.

@alberto1el
Copy link

IMHO have just 1 example per each component in each section, for example just (1) Hero Section component under the Page Sections, and have an example for that in multiple frameworks (Vue, React, Alpine, Svelte, Ember, Angular, JQuery).

The copy in the TailwindUI site says "Tailwind UI is a collection of professionally designed, pre-built, fully responsive HTML snippets you can drop into your Tailwind projects", in real life, Tailwind projects are not only HTML/React/Alpine/etc, I understand there are many JS FWs but having at least those makes sense, and to me makes more sense to make just 1 component in multiple frameworks rather than a lot of examples of the same component in just HTML which do not allow to just drop into any tailwind project, I know you have put a lot of thought into each decision (all the way back from TailwindCSS) this is just another option, later when all components have at least 1 example, then maybe start adding another option for each, and so on.

@stephenfrank
Copy link

Given small scope of each component and similarity of each example, it would be conceivable to write a compiler to convert each example and provide it in 3-4 different frameworks. However, this doesn't address the point that you don't necessarily want developers to copy the examples and use them as-is... I guess that's a much bigger discussion you'll have to have about how Tailwind UI should be used and integrated into projects.

@kendalled
Copy link

kendalled commented Apr 12, 2020

I honestly think Alpine and Comment-only examples are the way to go, considering that you would have to have the source for both to keep the interactive examples working. I use vue and got used to translating alpine fairly quickly. However, stuff like the click.away modifier had to be implemented completely differently using vue, which is where i would have preferred a more generic comment. I see the case for both sides, so I think the best route would be to show both. It also helps for learning, as the comments may rationalize some of the toggles/behavior which helps the user understand what's going on! I without a doubt think that comment only helps with "taking ownership of the code", like you talked about in your podcast @adamwathan big fan of your work! that's just my perspective.

@philipimperato
Copy link

Can you support Angular as well?

Will Tailwindui get hurt by maintaining 5 JS frameworks?

@RXminuS
Copy link

RXminuS commented Apr 13, 2020

Wow, I underestimated how many people instantly jump to the conclusion that Tailwind “supports” or doesn’t support a particular framework. Thought it would be obvious that the user needs to put in the work but we find a common language that makes it easy to do so.

I would have been in favor of keeping alpine (the comments feel way to verbose) as well as some simple templating to swap out blocks of HTML. Especially since my biggest pain has been figuring out repeated structures and classes more than “decoding what alpine does”.

But showing any kind of code or templating might hurt people giving TailwindUI a shot because they don’t connect the dots themselves.

What if you posted all the code as a Pastebin or Codebox example instead of static text? Maybe that makes it feel more fluid and ‘hacked together’?

Then if there’s some repeated structure (for instance user profiles) I would break them out into a ‘data’ array and loop over it so it becomes more clear what is repeated/copied.

Also, the SVG’s unlined get very verbose, often hurting readability? Is there something we can do to tidy those up? Import as images and provide the svg as a comment?

@itpropro
Copy link

...
I would have been in favor of keeping alpine (the comments feel way to verbose) as well as some simple templating to swap out blocks of HTML. Especially since my biggest pain has been figuring out repeated structures and classes more than “decoding what alpine does”.

...

Also, the SVG’s unlined get very verbose, often hurting readability? Is there something we can do to tidy those up? Import as images and provide the svg as a comment?

These are also my biggest pains using Vue js. Deleting repeatable structures and abstracting the classes to the right element is the most time consuming work (including transitions). I also think that SVGs make it more unreadable sometimes, but the major issue for me is templating.

I think the best way for TailwindUI would be to have an interactive demo on the website to get a feeling how it could behave, but have the snippets more language oriented. I mean even if you just know the basics of react, Vue, alpine or something else, it should not be a problem to adapt, otherwise it would be wiser to invest a little more time into the basics of Tailwind and your framework instead of using a "component library" like TailwindUI.

Thank you @adamwathan for your work and for taking so much feedback! TailwindUI already saved me much time on many levels.

@mattneel
Copy link

mattneel commented Apr 14, 2020

I love Tailwind. So thanks for it, @adamwathan. Have you considered using something like Stencil or another web components compiler to package 'standardized' examples as web components? I feel like you'd get the best of both worlds, as web components are pretty easily included in any (or no) framework (usually just a polyfill away).

@gilesbutler
Copy link

Just adding another vote for the comment only version. Totally agree with @smozely - the comments only version makes things a lot easier than having to rip out the Alpine examples.

@GDanielRG
Copy link

GDanielRG commented Apr 22, 2020

After using Tailwind UI for the past weeks by using it in real project with clients of myself, and writing an internal component library for these components (I've really given it a thought), this is what I have to say:

What really got me into Tailwind UI in the first place, was the idea of being able to build beautiful products easily, but what set this apart from another UI components package was that I felt that I could (and it also kinda of felt that I 'should', or something that was 'encouraged' by the product itself) modify and change the components as needed. Maybe this thought was enhanced by the fact that the code felt really familiar to me since I've been using Tailwind for the past few years and also Vue (first example components sent via email where with Vue). So whenever I looked at an example sent via email, I felt so happy that even then with a preview, not the actual content yet, I was really able to copy/paste and actually be using on my products right there and then, changing what I had to to make it work with my project and voila! After the early access was released I definitely noticed the difference from when I was able to just copy/paste, to now having to translate from Alpine to Vue (not really a big deal). What worries me is that since some other frameworks could be really different from alpine and Vue, having the alpine code in the html code you provide could be a problem since maybe other frameworks need a lot of other things or different html structure to make whatever the component was doing to work. (A very simple example I have faced is the transition animations, in alpine is as easy as adding come classes and in Vue you need a transition tag, as far as I am concerned). So the comments only version could really be better for those scenarios, while the alpine version will definitely be better for Alpine, Vue and some-other-syntax-similiar-framework users.

I'd love to come up with a way to give people copy & paste snippets that are closer to the syntax of their framework of choice, but I don't know how I can possibly do that without also misleading people to believe they are bullet-proof production-ready JS implementations, and actually providing that is just way out of scope for what we're working on right now.

After thinking about that and reading your comment above, it really hit me that moving everything to comments only version will certainly remove the problem that I presented above, the experience now won't vary that much from framework to framework, everyone will need to write some code in their own framework to 'make a dropdown work', but all of this to me raises a bigger question.

...but I don't know how I can possibly do that without also misleading people to believe they are bullet-proof production-ready JS implementations,

When I read that it really shocked me that the components you are giving are obviously not bulletproof, but unconsciously I was treating them as such! The part of not being able to provide them as bullet-proof production-ready JS is a business decision? or why is this not happening? as you can see from my first paragraph above, what really generated value and set this apart from everything was the feeling of being able to just copy paste and just modify what was needed, I really didn't felt that there was something missing to the component itself. So now I am thinking that to me the answer that really brings this product to its better version (to my eyes) is whichever decision helps you get to a bulletproof production-ready example. It doesn't matter if its with alpine with Vue or with comments only, but I would love to know that after 'translating' everything thats going on in the code you provided it will be production ready, or if the comments-only version is the one presented, would love to have all the comments, instructions, annotations etc. that need to be considered so the component gets to the production-ready state. As long as that statement is fulfilled with any version you provide its excellent. At the end I believe the product is targeted at developers, so whether they are able to translate them or not to their own framework shouldn't be your concern I think), but were you can really make a difference is by providing the guidelines to make it production-ready (comments only version) or the complete final component itself in any framework you choose to (I think alpine is ok). Both solutions of course would be the best. Having the complete comments-only version to make it production ready, and maybe a code example with alpine since you are already coding that to showcase it ;)

Thanks for all of your hard work on every product you have released!

@astrocket
Copy link

Any plan to add stimulus examples?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment