A quick tutorial to get you up and running with Essence UI Primitives.
Implementing a Popover
In this tutorial we will structure and style the Popover primitive.
1. Use the library
Add Essence UI to your Phoenix project:
{:essence_ui, "~> 0.2.0"}
Register LiveView hooks from deps/essence_ui/assets/js/essence_ui.js, then alias the primitive in your LiveView or function component module:
alias EssenceUI.Primitives.Popover
2. Compose the parts
Import and structure the parts. Each primitive is a module with named function components (root, trigger, content, …).
<Popover.root id="getting-started-popover">
<Popover.trigger id="getting-started-popover-trigger" content_id="getting-started-popover-content">
More info
</Popover.trigger>
<Popover.content id="getting-started-popover-content" side_offset={5}>
Some more info…
<Popover.arrow />
</Popover.content>
</Popover.root>
3. Add your styles
Primitives are unstyled. Pass class (and write CSS) for presentation. Demo styles live under assets/css/primitives/.
<Popover.root id="docs-gs-popover">
<Popover.trigger
id="docs-gs-popover-trigger"
content_id="docs-gs-popover-content"
class="DemoIconButton"
aria-label="Update dimensions"
>
+
</Popover.trigger>
<Popover.content id="docs-gs-popover-content" class="DemoPopoverContent" side_offset={5}>
<p class="DemoText" style="margin-bottom: 10px;">Dimensions</p>
<Popover.close class="DemoPopoverClose" aria-label="Close">×</Popover.close>
<Popover.arrow class="DemoPopoverArrow" />
</Popover.content>
</Popover.root>
/* reset */
button,
fieldset,
input {
all: unset;
}
.DemoPopoverContent {
border-radius: 4px;
padding: 20px;
width: 260px;
background-color: white;
box-shadow:
hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
will-change: transform, opacity;
}
.DemoPopoverContent:focus {
box-shadow:
hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
hsl(206 22% 7% / 20%) 0px 10px 20px -15px,
0 0 0 2px var(--violet-7);
}
.DemoPopoverContent[data-state="open"][data-side="top"] {
animation-name: slideDownAndFade;
}
.DemoPopoverContent[data-state="open"][data-side="right"] {
animation-name: slideLeftAndFade;
}
.DemoPopoverContent[data-state="open"][data-side="bottom"] {
animation-name: slideUpAndFade;
}
.DemoPopoverContent[data-state="open"][data-side="left"] {
animation-name: slideRightAndFade;
}
.DemoPopoverArrow {
fill: white;
}
.DemoPopoverClose {
font-family: inherit;
border-radius: 100%;
height: 25px;
width: 25px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--violet-11);
position: absolute;
top: 5px;
right: 5px;
}
.DemoPopoverClose:hover {
background-color: var(--violet-4);
}
.DemoPopoverClose:focus {
box-shadow: 0 0 0 2px var(--violet-7);
}
.DemoIconButton {
font-family: inherit;
border-radius: 100%;
height: 35px;
width: 35px;
display: inline-flex;
align-items: center;
justify-content: center;
color: var(--violet-11);
background-color: white;
box-shadow: 0 2px 10px var(--black-a7);
}
.DemoIconButton:hover {
background-color: var(--violet-3);
}
.DemoIconButton:focus {
box-shadow: 0 0 0 2px black;
}
.DemoFieldset {
display: flex;
gap: 20px;
align-items: center;
}
.DemoLabel {
font-size: 13px;
color: var(--violet-11);
width: 75px;
}
.DemoInput {
width: 100%;
display: inline-flex;
align-items: center;
justify-content: center;
flex: 1;
border-radius: 4px;
padding: 0 10px;
font-size: 13px;
line-height: 1;
color: var(--violet-11);
box-shadow: 0 0 0 1px var(--violet-7);
height: 25px;
}
.DemoInput:focus {
box-shadow: 0 0 0 2px var(--violet-8);
}
.DemoText {
margin: 0;
color: var(--mauve-12);
font-size: 15px;
line-height: 19px;
font-weight: 500;
}
@keyframes slideUpAndFade {
from {
opacity: 0;
transform: translateY(2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideRightAndFade {
from {
opacity: 0;
transform: translateX(-2px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes slideDownAndFade {
from {
opacity: 0;
transform: translateY(-2px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideLeftAndFade {
from {
opacity: 0;
transform: translateX(2px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.DemoButton {
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 4px;
padding: 0 15px;
font-size: 15px;
line-height: 1;
font-weight: 500;
height: 35px;
background-color: white;
color: var(--violet-11);
box-shadow: 0 2px 10px var(--black-a7);
}
.DemoButton:hover {
background-color: var(--violet-3);
}
.DemoButton:focus {
box-shadow: 0 0 0 2px black;
}
.DemoPopoverRow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
4. Next steps
- Read the styling guide for
classanddata-statepatterns - Browse components for APIs and examples
- Use Themes when you want a styled design system on top of these primitives
Summary
- Alias the primitive module
- Compose
Root/Trigger/Content(and other parts) in HEEx - Style with your own CSS using classes and data attributes