Tags, attributes, & text
Phlex is a lightweight HTML abstraction. Understanding how it maps to the generated HTML tags, attributes, comments, and other entities is foundational to creating quality components. We’ll also cover the built-in security and language server features in Phlex that make it a joy to use Phlex in your favorite code editor.
Tags & entities
If you dig deep into the Phlex source code, you’ll find a list of HTML tags registered as methods by Phlex. That’s why the p method corresponds to the <p> HTML tag, h1 to the <h1> HTML tag, etc.
Here’s what it looks like inside of the Phlex source:
# Outputs an `<a>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/a)
# [Spec](https://html.spec.whatwg.org/#the-a-element)
register_element def a(**attributes, &content) = nil
# Outputs an `<abbr>` tag.
#
# [MDN Docs](https://developer.mozilla.org/docs/Web/HTML/Element/abbr)
# [Spec](https://html.spec.whatwg.org/#the-abbr-element)
register_element def abbr(**attributes, &content) = nil
Calling these entities from a Phlex component looks like this:
class Component < Phlex::HTML
def view_template
h1 { "Hi! I'm an <h1></h1> tag" }
p { "And I'm a <p></p> tag"}
end
end
Attributes
In Phlex, attributes are keyword arguments passed into a tag. Consider the following example:
div(class: "container", id: "main"){ "Hello"}
This will generate the following HTML:
<div class="container" id="main">Hello</div>
class attributes
The class attribute can accept an array of strings, which is particularly useful when working with utility classes or if you need to conditionally set classes if an element is active, for example.
class Components::TitleCard < Components::Card
def title = nil
def subtitle = nil
def large? = false
def around_template(&)
super do
h1(id: "card-id", class: [
"font-bold",
(title_size_classes if large?),
title_color_classes
]){ title }
end
end
def title_size_classes = "text-2xl"
def title_color_classes = "text-blue-500"
end
This snippet shows how the h1(class: []) attribute accepts an array with conditionals.
Style attributes
Style attributes also get special treatment. In Phlex, you can set a style attribute by passing a hash as the value. For example:
div(style: { color: "red", background: "blue" }){ "Hello"}
This will generate the following HTML:
<div style="color:red;background:blue;">Hello</div>
This is particularly useful when styling components for email templates since email clients don’t work with linked or embedded stylesheets.
Here’s what it looks like in our component:
class Components::TitleCard < Components::Card
def title = nil
def subtitle = nil
def around_template(&)
super do
h1(id: "card-id", class: [
"font-bold",
title_size_classes,
title_color_classes
],
style: {
"margin-bottom" => "1rem",
"padding" => "1rem",
margin_top: "1rem"
}) { title }
h2 &:subtitle
# yield
render(&)
end
end
def title_size_classes = "text-2xl"
def title_color_classes = "text-blue-500"
end
Data attributes
Data attributes are attributes that start with data-. In Phlex, you can set a data attribute by passing a symbol as the key. For example:
div(data: { foo: "bar", baz: "qux" }){ "Hello"}
This will generate the following HTML:
<div data-foo="bar" data-baz="qux">Hello</div>
In Rails and Phlex applications, you’ll likely encounter data tags when using Hotwire. For example, a Turbo delete request on an a link might look like this:
a(href: url_for(@person), data: {turbo: { method: :delete }}){ "Delete person" }
Boolean attributes
Boolean attributes are attributes that can be present or absent. In Phlex, you can set a boolean attribute by passing true as the value. For example:
input(type: "checkbox", checked: true)
This will generate the following HTML:
<input type="checkbox" checked>
If you need the attribute to say “true”, you have to pass in the string “true”:
input(type: "checkbox", checked: "true")
This will generate the following HTML:
<input type="checkbox" checked="true">
Text, comments, and whitespace
Occasionally you may need to construct a sentence in Phlex with spans, links, etc. Consider the following in a footer.
p {
"Copyright Acme, Inc. #{Date.today.year}"
}
How would we set up a link to the company within the footer? Phlex has several methods that help us build up blocks that are more complex than a simple string.
p {
plain "Copyright"
whitespace
a(href: "/") { "Acme, Inc." }
whitespace
plain Date.today.year
}
The plain method emits plain text and whitespace emits a space into the HTML buffer. Is that more verbose? Yep, you bet it is. Generally you’ll write very little of this code in your Phlex components.
A common mistake
Occasionally when I’m breaking apart a block I forget to call the plain method. In the example below, the year of the date would return and be rendered in the p tag and everything else would be ignored.
# This will NOT work!!!
p {
"Copyright"
a(href: "/") { "Acme, Inc." }
Date.today.year
}
It’s important to remember when building up a block manually to call the plain and whitespace methods.
Safety and security
Phlex is designed to be safe and secure by default. It escapes all user input to prevent XSS attacks. It also provides a way to mark content as safe, allowing you to bypass the escaping mechanism.
JavaScript in attributes
JavaScript used in attributes must be marked as safe; otherwise, Phlex raises an exception.
# This will raise an exception
a(href: url_for(@person), onclick: "alert('You are deleting a person')"){ "Delete person" }
Inline JavaScript attributes must be marked as “safe” to work in Phlex, which means you need to make sure you have properly escaped your JavaScript to prevent security vulnerabilities like cross-site scripting attacks.
# Marked as safe since I know there won't be XSS vulnerabilities
a(href: url_for(@person), onclick: safe("alert('You are deleting a person')")){ "Delete person" }