Jan 19, 2022 • By Robin Mannering
A practical example of using the Donkeytail plugin with Tailwind and Alpine JS

Overview
In this article we'll explore a practical example of using the Donkeytail plugin to display points of interest (pins) on an image of a lounge, highlighting the different pieces of furniture for sale and labelling the name of each item with a short description and price.
We'll use Tailwind to style each Donkeytail pin, and Alpine JS to provide some interactivity that will open and close each pin to reveal the name, description and price.
For the sake of brevity we'll assume you already have a preferred method of integrating both Tailwind and Alpine JS into your Craft project.
Here's what we are creating...
Installation
Let's begin by installing the Donkeytail plugin.
- Open your terminal and go to your Craft project:
cd /path/to/project
- Then we'll use Composer and Craft to install the Donkeytail plugin:
composer require simplygoodwork/craft-donkeytail
php craft plugin/install craft-donkeytail
Create a new channel
Now that we've installed the Donkeytail plugin we can begin the initial Craft configuration.
As mentioned earlier, for each Donkeytail pin we'd like to display the following:
- furniture name
- short description
- price
We'll begin by setting up a new channel section, leaving both the Entry URI Format and Template empty.
For now we’ll assume that you’re using Donkeytail in a single site installation, so please ignore the French site.

We'll setup the Entry type as shown below:
- Short description is a plain text field
- Price is a number field
Set these up as desired using new fields, or reusing existing fields you have already defined.

Create a new field
Let's set up a new field that we'll use for dropping Donkeytail into our Craft templates. Since a picture is worth a thousand words we'll use the image below as a guide for setting up this field.
The Entry Sources field is of most interest, and we'd like to select from the channel we've just created which is named Donkey Tail Pins.

Add the Donkeytail field into any existing page.
We'll drop the newly created Donkeytail field into an existing page. In this example we'll add it to the homepage (which is a standard single section).

Add a canvas and your first pin.
Grab an image from Unsplash, or your preferred stock image library, and add it to the Donkeytail field in your homepage.
In Craft 3.7.x you can also add your first pin while editing the homepage, but in earlier versions you may need to add a Donkeytail pin entry first, and then add it to your homepage Donkeytail field.
As you add each pin you'll see the pin placed directly in the center of the canvas. Use your mouse to drag and drop to the desired position. We positioned our first pin bottom left on the sofa as indicated below.

And finally we can begin coding!
Now that we've wired everything up in Craft let's begin by dropping in the following code into the relevant template.
{% extends '_layout' %}
{% set donkeytail = entry.donkeytail %}
{% set donkeyTailCanvas = donkeytail.canvas ?? false %}
{% block content %}
<div class="container relative">
{% if donkeyTailCanvas %}
{% do donkeyTailCanvas.setTransform({ width: 1024 }) %}
{{ tag('img', {
src: donkeyTailCanvas.url,
width: donkeyTailCanvas.width,
height: donkeyTailCanvas.height,
srcset: donkeyTailCanvas.getSrcset(['1.5x', '2x', '3x']),
alt: 'Lounge',
class: 'w-full'
}) }}
{% for pin in donkeytail.pins %}
{% set pinTitle = pin.element.title %}
{% set pinShortDescription = pin.element.shortDescription %}
{% set pinPrice = pin.element.price %}
<div class="absolute"
style="transform:translate(-50%, -50%);
top:{{ pin.y }}%;
left:{{ pin.x }}%;">
{# open svg #}
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="18.75" stroke="#ffffff" stroke-width="2.5"/>
<path d="M20 10V30" stroke="#ffffff" stroke-width="2.5"/>
<path d="M10 20H30" stroke="#ffffff" stroke-width="2.5"/>
</svg>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}
The above code will take care of the basics:
- placing the Donkeytail canvas on the page
- iterating each Donkeytail pin and placing an SVG in the appropriate position
The relative class on the container div, and the nested absolute class, are of particular importance here in the successful positioning of the pins.
The style attribute for each pin contains a transform to re-center the SVG so that its center sits on the pin position defined by top and left.

Directly after the absolutely positioned pin/svg, let's add what will become our popover containing the name, description and price of the item.
<div class="absolute z-20 bg-white p-6 w-72 shadow"
style="top:{{ pin.y }}%; left:{{ pin.x }}%;
transform:translate(-50%, 60px);">
{# rotate white square to add a triangle #}
<div class="absolute bg-white z-10
-top-5 h-10 w-10 transform rotate-45"
style="left: 7.75rem;">
</div>
<div class="font-bold">{{ pinTitle }}, {{ pinPrice}}USD</div>
<div class="mt-1">{{ pinShortDescription }}</div>
</div>

Alpine JS to the rescue
We don't want the popover displaying for all our pins, which could lead to a very cluttered image with a lot of the image hidden behind information.
Let's begin (amending the existing code) to add a little interactivity using a sprinkling of Alpine JS adding:
x-data
x-show
x-on:click
Step 1
Everything in Alpine starts with the x-data
directive.
<div x-data="{ selected: '' }"
class="container relative">
{% if donkeyTailCanvas %}
Step 2
Set selected
to the clicked pinId
using x-on:click
<div x-on:click="selected === '{{ pinId }}'
? selected = ''
: selected = '{{ pinId }}'"
class="absolute"
style="transform:translate(-50%, -50%);
top:{{ pin.y }}%;
left:{{ pin.x }}%;">
Step 3
Inside the for loop, if the current pin has been selected show the close svg icon else show the open svg icon.
{# open svg #}
<svg x-show="selected != '{{ pinId }}'"
width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="18.75" stroke="#ffffff" stroke-width="2.5"/>
<path d="M20 10V30" stroke="#ffffff" stroke-width="2.5"/>
<path d="M10 20H30" stroke="#ffffff" stroke-width="2.5"/>
</svg>
{# close svg #}
<svg x-show="selected === '{{ pinId }}'"
width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" stroke="#ffffff" stroke-width="2.5" />
<path d="M10 20H30" stroke="white" stroke-width="2.5"/>
</svg>
Finally
Only show the popover for the currently selected pin.
{# popover #}
<div x-show="selected === '{{ pinId }}'"
class="absolute z-20 bg-white p-6 w-72 shadow"
style="top:{{ pin.y }}%; left:{{ pin.x }}%;
transform:translate(-50%, 60px);">
That's it, we're done folks.
Below is a copy of the full source code created in this post.
{% extends '_layout' %}
{% set donkeytail = entry.donkeytail %}
{% set donkeyTailCanvas = donkeytail.canvas ?? false %}
{% block content %}
<div class="container relative"
x-data="{ selected: '' }">
{% if donkeyTailCanvas %}
{% do donkeyTailCanvas.setTransform({ width: 1024 }) %}
{{ tag('img', {
src: donkeyTailCanvas.url,
width: donkeyTailCanvas.width,
height: donkeyTailCanvas.height,
srcset: donkeyTailCanvas.getSrcset(['1.5x', '2x', '3x']),
alt: 'Lounge',
class: 'w-full'
}) }}
{% for pin in donkeytail.pins %}
{% set pinId = pin.element.id %}
{% set pinTitle = pin.element.title %}
{% set pinShortDescription = pin.element.shortDescription %}
{% set pinPrice = pin.element.price %}
<div x-on:click="selected === '{{ pinId }}'
? selected = ''
: selected = '{{ pinId }}'"
class="absolute"
style="transform:translate(-50%, -50%);
top:{{ pin.y }}%;
left:{{ pin.x }}%;">
{# open svg #}
<svg x-show="selected != '{{ pinId }}'"
width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="18.75" stroke="#ffffff" stroke-width="2.5"/>
<path d="M20 10V30" stroke="#ffffff" stroke-width="2.5"/>
<path d="M10 20H30" stroke="#ffffff" stroke-width="2.5"/>
</svg>
{# close svg #}
<svg x-show="selected === '{{ pinId }}'"
width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="20" cy="20" r="20" stroke="#ffffff" stroke-width="2.5" />
<path d="M10 20H30" stroke="white" stroke-width="2.5"/>
</svg>
{# popover #}
<div x-show="selected === '{{ pinId }}'"
class="absolute z-20 bg-white p-6 w-72 shadow"
style="top:{{ pin.y }}%; left:{{ pin.x }}%;
transform:translate(-50%, 60px);">
{# rotate white square to add a triangle #}
<div class="absolute bg-white z-10
-top-5 h-10 w-10 transform rotate-45"
style="left: 7.75rem;">
</div>
<div class="font-bold">{{ pinId }}, {{ pinTitle }}, {{ pinPrice}}</div>
<div class="mt-1">{{ pinShortDescription }}</div>
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}