TailwindCSS Template Card - build custom cards with HTML and TailwindCSS

Top job! Thank you very much. As it can be loaded from CDN, maybe we can download all the packages and include them in yaml pointing to a local repository?

Yeah, it’s possible! That’s what i meant when i said about custom url for download

1 Like

Check out the version v1.4.1! It brings some new tweaks for the card and DaisyUI usage, as well as a brand new visual config editor, built with DaisyUI!!!

tailwindshort

This is awesome :ok_hand:. U rock mate! I’m going to give this a try next weekend. This is opening the door to some real nice custom designs. Woop woop.

1 Like

I’m building a card for my lights using a daisyui slider. I can see the elements in the frontend, so that’s fine, but now I’m stuck. I can call the service providing a hard coded brightness_pct, but I have no clue how to get the value of the slider in the Hass.callservice. Do you have something to guide me or do you know how to use javascript functions in your component?

1 Like

Awesome! That’s actually very easy to accomplish: you need to get the value from HomeAssistant with Jinja templates. It will update your card with the new state whenever your entities updates. e.g.:

<input
    type="range"
    class="range range-accent"
    min="0" max="100"
    value={{ states('input_number.test') }}
    onChange="hass.callService('input_number', 'set_value', { entity_id: 'input_number.test', value: this.value })"
/>

Thx for the reply. Do I need to create a home assistant element for this? I don’t want to create separate home assistant elements for this. I can put the value of a home assistant element I the range selector from tailwind. That’s already working.

Now I would like to get the value from the tailwind range selector and put that value in the hass.callservice of a button to set the brightness of my light according to the selected value of the tailwind range selector.

Nah, you don’t need to create a separated element for it, you can do it all with a single html except with tailwindcss-template-card

About sending the values from tailwind to hass services, check the example i posted earlier here, it contains a slider based on input[type="range"] that automatically changes its value following the input_number.test value and tells to home assistant, with callService, the slider value when you interact with it.

So it’s a two-way synced element, if that makes sense. The rendered HTML will update itself on any entity state change automatically (if you use the entity in jinja templates within your html content) and you can pass the any values from your HTML to home assistant with the hass object in javascript.

That’s made with simple javascript: use this.value inside the onChange handler.

References:
About the object this in inline event handlers: this - JavaScript | MDN
About the object hass: Frontend data | Home Assistant Developer Docs

Another example of two-way synced elements:
toggle

In the example above, the first card is a native Tile card and the second one is a tailwindcss-template-card.

HTML Content:

<div class="flex flex-col">
  <div class="form-control h-12 justify-center w-full">
    <label class="cursor-pointer label">
      <span class="label-text">{{state_attr('input_boolean.notifications','friendly_name')}}</span> 
      <input type="checkbox" class="toggle toggle-primary" {{ iif(is_state('input_boolean.notifications', 'on'), "checked", "") }} onChange="hass.callService('homeassistant', 'toggle', { entity_id: 'input_boolean.notifications' })" />
    </label>

The {{ iif(is_state('input_boolean.notifications', 'on'), "checked", "") }} will insert the attribute “checked” to the input tag if the state of input_boolean.notifications equals to “on”. That’s called immediate if and it’s a function from Jinja.

Note that i don’t pass any value to callService because it can only be false or true, so i just call toggle. And the input will always be updated with the value from home assistant, so i don’t have to worry about the card showing true while the entity value it’s actually false. I just call toggle and let Jinja do its job to fetch the updated value and refresh the card.

Reference:
Immediate If (IIF): Templating - Home Assistant

This is a GIF of the first example i sent:
slider-tw

The DaisyUI theme used here is halloween, if anyone is interested

Hi mate. We’re not on the same page here :grin:. I want to be able to adjust the brightness of a light using a range slider. Here’s my code:

<div class="card w-full bg-base-100 shadow-xl">
  <figure><img src="/local/images/ui_elements/lamp-kort.png" alt="Keuken" /></figure>
  <div class="card-body">
    <h2 class="card-title">
      Keuken
      <div class="badge badge-secondary"> {{ 'fout' if is_state('light.lichten_keuken', 'unavailable') else ''}} </div>
    </h2>
    <p>This is a light card wip</p>

    <div class="card-actions"> 
<button class="btn btn-wide {{ 'btn-success' if is_state('light.lichten_keuken', 'on') else ''}}" onClick="hass.callService('homeassistant', 'toggle', {entity_id: 'light.lichten_keuken'})">{{ 'aan' if is_state('light.lichten_keuken', 'on') else 'uit'}}</button>
<div class="flex">
  <div class="flex-auto w-64">
    <input type="range" min="0" max="100" value="70" class="range range-lg pr-10" />
  </div>
  <div class="flex-auto w-32">
    <input type="checkbox" class="toggle toggle-success toggle-lg" {{ 'checked' if is_state('light.lichten_keuken', 'on') else 'unchecked'}} onClick="hass.callService('homeassistant', 'toggle', {entity_id: 'light.lichten_keuken'})" />
  </div>
  
</div>


</div>
<div class="card-actions justify-end"> 
      <div class="badge badge-outline">{{expand('light.lichten_keuken')|selectattr('state','eq','on')|list|count}} / {{expand('light.lichten_keuken')|list|count}}</div> 
      <div class="badge badge-outline">
{{states('light.lichten_keuken')}} 
{% if(is_number(state_attr('light.lichten_keuken', 'brightness')))%}
{% set bri= state_attr('light.lichten_keuken', 'brightness')%}
{% set bri = (bri/2.55)|round(0)%}
{%else%}
{% set bri=0%}
{%endif%}
{{bri}}
%</div>
    </div>
  </div>
</div>

How can I get the changes I make using the slider in tailwind to trigger a change on the light brightness? I can toggle a light using tailwind and I can set a fixed brightness, but i don’t know how to set the brightness based on the value of the slider.

I hope this feedback will give you some guidance. I really love this approach and I strongly believe this project has huge potential for nice dashboards.

Currently I’m seeing a lot of latency and I didn’t build many components in the same view yet. Benchmarking is done comparing tailwind elements in my view with native elements from home assistant in the same view.

  1. Very often tailwind components are not loaded at all or very slow. Native components show instantly.
  2. Changing entity status is not immediately visible in the tailwind elements (using the predefined classes to reflect a change). Ex: green color of a button means status is on and no color is reflecting status off.
  3. Changing status of a device (only tested light) is picked up instantly using the onclick event with the hass services.

Would the impact of running this setup locally (not loading the entire framework over and over again from the cloud) have a positive result?
Can we cache the tailwind framework once loaded to speed up loading times?

Thanks for the feedback! Which version are you using! The latest v2.0.3 brings some interesting performance improvements

Updated to v2.0.3 and will retest. Feedback will follow soon.

Is the onchange event on an input element that is used with a drag not going to fire every change it sees? In other words, if I drag the slider from 0 to 255, is this going to hit the event 255 times and thus call the Hass service 255 times? Can we use something like focused?

Change event fires only when value has been committed (see here: HTMLElement: change event - Web APIs | MDN). In the case of a range input, only when you release.

@usernein This is awesome. This would make styling custom cards a breeze. I’ll give it a try in the next few days and report back.

One immediate concern or comment I have is embedding logic (JavaScript) inside the content itself. It would be nice if there was a way to have the chance to specify a script that could handle all the interaction with the HTML content you’re rendering.

I found this (New lovelace custom card approach - fully customizable card with your own design) a few minutes ago, where there’s a clear separation between content, binding and behavior. I don’t necessarily think binding is needed, but scripts (behavior) definitely is when you start working on more complex interactive cards.

I know this might go beyond the “style cards using Tailwind” approach shown here, but I’d love to see a solution to create custom cards in a more lightweight manner instead of creating a full-blown custom component.

Let me know what you think.

1 Like

Wow, this is absolutely awesome.

Is there any way to use the Home Assistant SVG icons (MDI for example)?

For example, you can just use this inside content:

<ha-icon icon="mdi:ceiling-fan-light"></ha-icon>

1 Like

@silvio we are on the same page! I
That’s exactly what i did on the first example: call a service on home assistant passing the value of the input. You just need to adjust it to use the home assistant service that you want.

But i did the work for you (and tested):

<div class="card w-full bg-base-100 shadow-xl">
  <figure><img src="https://picsum.photos/300/100" alt="Keuken" /></figure>
  <div class="card-body">
    <h2 class="card-title">
      Banheiro
      <div class="badge badge-secondary"> {{ 'fout' if is_state('light.banheiro', 'unavailable') else ''}} </div>
    </h2>
    <p>This is a light card wip</p>

    <div class="card-actions"> 
<button class="btn btn-wide {{ 'btn-success' if is_state('light.banheiro', 'on') else ''}}" onClick="hass.callService('homeassistant', 'toggle', {entity_id: 'light.banheiro'})">{{ 'aan' if is_state('light.banheiro', 'on') else 'uit'}}</button>
<div class="flex">
  <div class="flex-auto w-64">
    <input type="range" min="0" max="255" value={{ state_attr('light.banheiro', 'brightness') }} class="range range-lg pr-10" onChange="hass.callService('light', 'turn_on', {'entity_id': 'light.banheiro', brightness: this.value})"/>
  </div>
  <div class="flex-auto w-32">
    <input type="checkbox" class="toggle toggle-success toggle-lg" {{ 'checked' if is_state('light.banheiro', 'on') else 'unchecked'}} onClick="hass.callService('homeassistant', 'toggle', {entity_id: 'light.banheiro'})" />
  </div>
  
</div>


</div>
<div class="card-actions justify-end"> 
      <div class="badge badge-outline">{{expand('light.banheiro')|selectattr('state','eq','on')|list|count}} / {{expand('light.lichten_keuken')|list|count}}</div> 
      <div class="badge badge-outline">
{{states('light.banheiro')}} 
{% if(is_number(state_attr('light.banheiro', 'brightness')))%}
{% set bri= state_attr('light.banheiro', 'brightness')%}
{% set bri = (bri/2.55)|round(0)%}
{%else%}
{% set bri=0%}
{%endif%}
{{bri}}
%</div>
    </div>
  </div>
</div>

Note that i changed the input range to 0-255