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" }

Checkout in minutes

Use Apple Pay, Amazon Pay, or your credit card to order this course and we'll email you the receipt.

Purchase video course for $379 $379