Salesforce, Python, SQL, & other ways to put your data where you need it

Need event music? 🎸

Live and recorded jazz, pop, and meditative music for your virtual conference / Zoom wedding / yoga class / private party with quality sound and a smooth technical experience

Tailwind UI structure (PRIVATE)

10 Oct 2024
💬 EN

Table of Contents

Note to self: DO NOT PUBLISH. Would violate my Tailwind UI terms of service. Keep in non-search-indexed draft status just for showing to a colleague.

The 3 Tailwind Labs products

Tailwind Labs (Adam Wathan and the people he’s hired) makes 3 products:

  1. Tailwind CSS (free): a combination of a CSS preprocessor and a bunch of opinionated CSS “utility”-style class name definitions.
  2. Headless UI (free): an NPM-importable package of unstyled React components that can accept a plaintext string as a parameter and throw it into className.
  3. Tailwind UI (paid): a snippet library of HTML fragments that Tailwind Labs think look beautiful, based on the utility classes they strung together in the class property of the fragments’ elements. See below for details.

Tailwind UI

Here’s what you get when you buy Tailwind UI:

A website login for copy-paste HTML fragment examples

You get a login that makes it so code actually shows up when you surf the URLs under tailwindui.com/components.

So, instead of “soft buttons” having a “get the code” link, there’s a toggle that lets you actually see what it looks like as an HTML fragment, a React fragment, or a Vue fragment.

None of the fragments actually have any presentational JavaScript in them.

They’re just HTML (or framework-specific variants on HTML, like JSX) that’s marked up with a bunch of CSS class names.

Note: This means that the fragments you’ve just bought the right to see do not inherently meet accessibility needs that can only be solved with JavaScript.

For example, some design system designers’ want you to .preventDefault() for any <button> that contains an aria-disabled=true attribute.

Here are the 3 fragment variants for the Tailwind UI’s largest “soft button” as of 10/10/2024:

HTML button

<button 
    type="button" 
    class="
        rounded-md 
        bg-indigo-50 
        px-3.5 py-2.5 
        text-sm 
        font-semibold 
        text-indigo-600 
        shadow-sm 
        hover:bg-indigo-100
">Button text</button>

React button

export default function Example() { 

    const joinedClassNames = [
        'rounded-md',
        'bg-indigo-50',
        'px-3.5',
        'py-2.5',
        'text-sm',
        'font-semibold',
        'text-indigo-600',
        'shadow-sm',
        'hover:bg-indigo-100',
    ].join(' ')

    return ( 
        <>
            <button 
                type="button" 
                className={joinedClassNames}
            >Button text</button>
        </>
    )

}

Vue button

<template>
    <button 
        type="button" 
        class="
            rounded-md 
            bg-indigo-50 
            px-3.5 py-2.5 
            text-sm 
            font-semibold 
            text-indigo-600 
            shadow-sm 
            hover:bg-indigo-100
    ">Button text</button>
<template>

ZIP files with even more styling-only-no-behavior fragments

You also get the right to download a handful of ZIP files full of JSX fragment files giving you even more looks and feel examples.

Note: They have all of the same no-accessibility-oriented-client-side-JavaScript-built-in issues as the copy-paste ones from part 1 mentioned above.

button.jsx

Here’s the relevant part of the button.jsx file from the “Salient” theme ZIP file:

import Link from 'next/link'; 

export function Button( { className, ...props }) { 

    // some opinionated className beautifying here

    return typeof props.href === 'undefined' ? ( 
            <button 
                className={className} 
                {...props}
            /> 
        ) : ( 
            <Link 
                className={className} 
                {...props} 
            />
        )

}

1 ZIP file of fragments with Headless UI instead of HTML under the hood

Finally, you get the right to download a ZIP file (the “Catalyst” theme) full of JSX fragment files that’s a little different from the others.

It doesn’t invoke React’s abstraction of HTML’s <button/> data type like they do.

It invokes Headless UI’s <Headless.Button/> data type.

Note: In theory, because <Headless.Button/> is supposed to deliver accessibility-oriented client-side JS to the browser, the example code files from the Catalyst theme’s ZIP file might conform a little better to the spirit of your organization’s designers’ desires.

Whether they actually work is another matter – I’ve seen a few concerning bug reports in Headless UI:

  • “If a form was submitted, the checkbox data was not included. Clicking or interacting with the checkbox would not do anything.” -5/28/2024 pull request

  • “Constantly waiting on bug fixes.” -10/5/2024 redditor

Here’re more or less the relevant parts of 2 files from the “Catalyst” theme ZIP file that produce a button:

link.jsx

import * as Headless from '@headlessui/react'; 
import React, { forwardRef } from 'react';

export const Link = forwardRef(
    function Link(props, ref) { 
        return ( 
            <Headless.DataInteractive>
                <a 
                    {...props} 
                    ref={ref} 
                />
            </Headless.DataInteractive> 
        )
    }
)

button.jsx

import * as Headless from '@headlessui/react'; 
import React, { forwardRef } from 'react'
import { Link } from './link'; 

export const Button = forwardRef(

    function Button ( { className, children, ...props}, ref) {

        // some opinionated className beautifying here

        return 'href' in props ? (
                <Link 
                    {...props} 
                    className={className} 
                    ref={ref}
                >
                    <TouchTarget>
                        {children}
                    </TouchTarget>
                </Link> 
            ) : ( 
                <Headless.Button 
                    {...props} 
                    className={className} 
                    ref={ref}
                >
                    <TouchTarget>
                        {children}
                    </TouchTarget>
                </Headless.Button>
            )

    }

)

/**
 * Expand the hit area to at least 44x44px on touch devices
*/ 
export function TouchTarget({ children )} { 
    

    const joinedClassNames = [
        'absolute',
        'left-1/2',
        'top-1/2',
        'size-[max(100%,2.75rem)]',
        '-translate-x-1/2',
        '-translate-y-1/2',
        '[@media(pointer:fine)]:hidden]',
    ].join(' ')

    return (
        <>
            <span 
                className={joinedClassNames}
                aria-hidden="true"
            />
            {children}
        </>
    ) 

}
--- ---