Tailwind CSS

I just wanted to showcase how customizable Panel is. You can in fact build custom components including widgets and templates quite simple.

The below example shows how to create a custom Button using Tailwind CSS.

import panel as pn
import param

pn.extension(sizing_mode="stretch_width")

info = pn.pane.HTML("""
<div class="bg-indigo-900 text-center py-4 lg:px-4">
  <div class="p-2 bg-indigo-800 items-center text-indigo-100 leading-none lg:rounded-full flex lg:inline-flex" role="alert">
    <span class="flex rounded-full bg-indigo-500 uppercase px-2 py-1 text-xl font-bold mr-3">WOW</span>
    <span class="font-semibold mr-2 text-left flex-auto">Panel supports custom HTML components using Tailwind</span>
  </div>
</div>
""", height=100)
class CustomButton(pn.reactive.ReactiveHTML):
    clicks = param.Integer()
        
    _template = """\
<div id="pn-container" style="height:100%;width:100%">
<button id="button" class="bg-indigo-500 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded"
onclick="${_button_click}"
>{{ name }}</button></div>
"""
    
    __javascript__ = ["https://cdn.tailwindcss.com"]

    def _button_click(self, event):
        self.clicks += 1

button1 = CustomButton(name="Click Me", height=38)

@pn.depends(clicks=button1.param.clicks)
def text(clicks=1):
    return f"I've been clicked {clicks} number of times"

pn.Column(
    info, button1, text
).servable()

Resources

8 Likes

Thanks @Marc for the showcase. I have been using TailwindCSS together with ReactiveHTML to build a web app at work for geoscience data analysis and it works great.

Using a local tailwind config file and the JIT mode from tailwind can also output a very lightweight CSS file by automatically only include the css classes actually used in your Panel app.

3 Likes

Hi @Julien

Thanks for sharing. Would it be possible to share a screenshot? And describe in more detail how to output a very lightweight css file? And maybe even the source code?

I’m very curious to understand more.

Hi @Marc,

I cannot share the code unfortunately but I will use your example and setup tailwind as I did in my project. As I have some deadlines to meet for the next two days, I will try to properly answer you by the end of this week.

3 Likes

Hi @Marc

Please find below a repo with a similar tailwind setup as I am using for my project:

Right now there is an outage on PyPi and I could not install the python requirement using poetry. However the code should work once installed.

You can have a look at the src/dist.css file to see that only the soft reset (or preflight in tailwindcss wording) and the classes used in the app are being outputted. This is faster than using the tailwind cdn which uses the same approach and the JIT mode that only includes the class you use.

I hope it helps. Let me know if you need more info.

6 Likes

This is great @Julien . Did you get this to work with more complex things? I tried extending it with popovers/popups but the component would not render correctly (even after yarn css) or would not react to the clicks.

Also, it seems that the libs/notifications/notifications.css file is missing, not sure what the purpose is?

To get popovers working you can do something like the below

import panel as pn
import param

pn.extension(sizing_mode="stretch_width")

class CustomButton(pn.reactive.ReactiveHTML):
    clicks = param.Integer()
        
    _template = """\
<div id="container">
<button id="popover_button" onclick="${_button_click}" data-popover-target="popover_default" type="button" class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800">Default popover</button>
<div data-popover id="popover_default" role="tooltip" class="absolute z-10 invisible inline-block w-64 text-sm text-gray-500 transition-opacity duration-300 bg-white border border-gray-200 rounded-lg shadow-sm opacity-0 dark:text-gray-400 dark:border-gray-600 dark:bg-gray-800">
    <div class="px-3 py-2 bg-gray-100 border-b border-gray-200 rounded-t-lg dark:border-gray-600 dark:bg-gray-700">
        <h3 class="font-semibold text-gray-900 dark:text-white">Popover title</h3>
    </div>
    <div class="px-3 py-2">
        <p>And here's some amazing content. It's very engaging. Right?</p>
    </div>
    <div data-popper-arrow></div>
</div>
</div>
"""
    __css__ = ["https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.7.0/flowbite.min.css"]
    
    __javascript__ = ["https://cdnjs.cloudflare.com/ajax/libs/flowbite/1.7.0/flowbite.min.js"]

    _scripts = {
        "render": """
new Popover(popover_default, popover_button)
"""
    }

    def _button_click(self, event):
        self.clicks += 1

button1 = CustomButton(name="Click Me", height=38)

@pn.depends(clicks=button1.param.clicks)
def text(clicks=1):
    return f"I've been clicked {clicks} number of times"

pn.Column(
    button1, button1.param.clicks, margin=25
).servable()

That is awesome @Marc!.
In general it seems that when things don’t work as they would when copy-pasting examples from such frameworks into a standalone HTML it is generally because one needs to inject some extra javascript in the render of after_layout functions. Do you have a general thought/debugging process when trying to get these to work?

1 Like

Some tips & tricks based on my experience

  • With Panel 1.x the html _template of ReactiveHTML is inside shadowroot. It means HTML/ JS examples do not always work out of the box.
  • Create simple `.py´ with ReactiveHTML element
  • panel serve it with --autoreload
  • Start from very simple code. If you experience issues reduce to very simple code.
  • Monitor the console in the browser. It can be shown by right-clicking in the browser and selecting Inspect.
  • If HTML/ JS example does not work out of the box. Try to see how they use the js framework in React, Angular, Web components or similar for inspiration.
  • Use the HoloViz discourse when you run into issues.

When ever you get something working please share as showcase because it helps build the knowledgebase.

If you experience bugs please report them on github. It helps mature the framework.

1 Like