The frame and the text of an e-mail: the document with its <head>, the 600 px card with the brand above and the
footer below, the heading, the paragraph, the button and the copy-and-paste link.
An e-mail client has no Tailwind, no CSS variables and no flexbox: everything here is a table with inline styles,
and the hex values are the canvas's own, kept in one place (a private style module). The only stylesheet is the
<style> of document/1: the 600 px column, the narrow padding under 620 px and the dark mode, which the client
turns on with prefers-color-scheme and which restyles the elements that carry an em-* class (em-body,
em-card, em-ink, em-muted, em-soft, em-line, em-panel, em-btn-primary, em-btn-secondary).
Every text is yours: the components draw no words of their own, so one e-mail can be written in any language.
The sibling modules draw the rest: DevactionUI.EmailTag, DevactionUI.EmailCallout and DevactionUI.EmailTable.
document/1 wraps layout/1, and to_html/1 turns the result into the string a mailer sends:
<.document lang="en" title="Sign in">
<.layout preheader="..." brand_name="Acme" brand_href="https://acme.test" logo_src="https://acme.test/logo.png" signature="Acme Inc.">
<.heading>Sign in</.heading>
</.layout>
</.document>
Summary
Functions
The button of the e-mail: a table cell with a link in it, 12 px radius, which every client draws the same.
Two or three buttons side by side, each a little narrower than a single button/1.
A person and what they did: a round avatar with initials and a sentence beside it, above the heading.
The small mono caption above a block (a list, a table): 11 px, spaced, in the soft color. Write it in capitals.
A thin rule between the body and the fine print.
The whole HTML document of an e-mail: doctype, <head> with the viewport and color-scheme metas and the one
<style> the e-mail needs, and a <body> with the page background.
A strong run of text inside a paragraph, in the ink color. On the card it turns light in dark mode with the rest
of the ink; inside a tinted block, say surface={:tint} so it stays dark.
An underlined link for the footer paragraph: preferences, unsubscribe.
The title of the e-mail: 26 px, 600 weight, in the ink color.
The page of an e-mail: the hidden preheader, the brand, the white card and the footer, in a 600 px column.
The address to copy when the button does not work: a line of text and the URL in mono, breaking anywhere.
A paragraph of the e-mail.
The <style> of an e-mail: the 600 px column, the narrow padding and the dark rules. document/1 includes it;
a preview inside another page can place it by itself, with a dark_scope.
Renders a ~H result (or the output of a function component) to the HTML string a mailer sends.
Functions
@spec button(map()) :: Phoenix.LiveView.Rendered.t()
The button of the e-mail: a table cell with a link in it, 12 px radius, which every client draws the same.
:primary is the ink button (it turns light in dark mode), :secondary the outlined one.
Example
<.button href={@url}>Sign in</.button>
<.button href={@reset_url} variant={:secondary}>Reset password</.button>Attributes
href(:string) (required)variant(:atom) - Defaults to:primary. Must be one of:primary, or:secondary.- Global attributes are accepted. goes on the table that holds the button.
Slots
inner_block(required)
@spec button_row(map()) :: Phoenix.LiveView.Rendered.t()
Two or three buttons side by side, each a little narrower than a single button/1.
Example
<.button_row>
<:button href={@draft_url}>Draft the renewal</:button>
<:button href={@url} variant={:secondary}>Open contract</:button>
</.button_row>Attributes
- Global attributes are accepted.
Slots
button(required) - a button of the row. Accepts attributes:href(:string) (required)variant(:atom) - Must be one of:primary, or:secondary.
@spec byline(map()) :: Phoenix.LiveView.Rendered.t()
A person and what they did: a round avatar with initials and a sentence beside it, above the heading.
Example
<.byline initials="CA">
<.emphasis>Carlos</.emphasis> invited you to <.emphasis>Acme</.emphasis>.
</.byline>Attributes
initials(:string) (required) - the letters in the round avatar.- Global attributes are accepted.
Slots
inner_block(required) - the sentence beside the avatar.
@spec caption(map()) :: Phoenix.LiveView.Rendered.t()
The small mono caption above a block (a list, a table): 11 px, spaced, in the soft color. Write it in capitals.
Example
<.caption>WHAT TO LOOK AT</.caption>Attributes
- Global attributes are accepted.
Slots
inner_block(required)
@spec divider(map()) :: Phoenix.LiveView.Rendered.t()
A thin rule between the body and the fine print.
Example
<.divider />Attributes
- Global attributes are accepted.
@spec document(map()) :: Phoenix.LiveView.Rendered.t()
The whole HTML document of an e-mail: doctype, <head> with the viewport and color-scheme metas and the one
<style> the e-mail needs, and a <body> with the page background.
Example
<.document lang="en" title="Your link">
<.layout ...>...</.layout>
</.document>Attributes
lang(:string) (required) - the language of the e-mail, as inenorpt-BR.title(:string) (required) - the<title>, usually the subject.dark_scope(:string) - a CSS selector ([data-theme=dark]) that turns the dark rules on instead of theprefers-color-schemequery; for previews. Defaults tonil.- Global attributes are accepted. goes on the
<body>.
Slots
inner_block(required)
@spec emphasis(map()) :: Phoenix.LiveView.Rendered.t()
A strong run of text inside a paragraph, in the ink color. On the card it turns light in dark mode with the rest
of the ink; inside a tinted block, say surface={:tint} so it stays dark.
Example
<.paragraph>The link works for <.emphasis>15 minutes</.emphasis>.</.paragraph>Attributes
surface(:atom) -:tintinside a callout, whose light background stays light in dark mode and so must keep the dark ink. Defaults to:card. Must be one of:card, or:tint.- Global attributes are accepted.
Slots
inner_block(required)
@spec heading(map()) :: Phoenix.LiveView.Rendered.t()
The title of the e-mail: 26 px, 600 weight, in the ink color.
Example
<.heading>Sign in to Acme</.heading>Attributes
- Global attributes are accepted.
Slots
inner_block(required)
@spec layout(map()) :: Phoenix.LiveView.Rendered.t()
The page of an e-mail: the hidden preheader, the brand, the white card and the footer, in a 600 px column.
Each footer slot is one paragraph; put a footer_link/1 in it for preferences or unsubscribe.
Example
<.layout
preheader="Click to sign in."
brand_name="Acme"
brand_href="https://acme.test"
logo_src="https://acme.test/logo.png"
signature="Acme Inc. · Springfield"
>
<.heading>Sign in</.heading>
<:footer>You got this because you have an account.</:footer>
</.layout>Attributes
preheader(:string) (required) - the line a mail list shows after the subject; hidden in the body.brand_name(:string) (required) - the name beside the logo.brand_href(:string) (required) - where the logo and the name lead.logo_src(:string) (required) - an absolute URL of the logo, a square image.logo_size(:integer) - the logo's side in px. Defaults to36.signature(:string) (required) - the last footer line: the company and where it is.- Global attributes are accepted.
Slots
inner_block(required) - the content of the card.footer- a paragraph of the footer above the signature; give one per line (why the e-mail came, links).
@spec link_fallback(map()) :: Phoenix.LiveView.Rendered.t()
The address to copy when the button does not work: a line of text and the URL in mono, breaking anywhere.
Example
<.link_fallback href={@url}>If the button does not work, paste this address in your browser:</.link_fallback>Attributes
href(:string) (required) - the address, shown as text and linked.- Global attributes are accepted.
Slots
inner_block(required) - the line above the address.
@spec paragraph(map()) :: Phoenix.LiveView.Rendered.t()
A paragraph of the e-mail.
Example
<.paragraph>Click the button. The link works <.emphasis>once</.emphasis>.</.paragraph>
<.paragraph size={:small} flush>If it was not you, ignore this e-mail.</.paragraph>Attributes
size(:atom) -:body16 px,:compact15 px,:small13 px in the soft color for the fine print. Defaults to:body. Must be one of:body,:compact, or:small.lead(:boolean) - the ink color, for the greeting line. Defaults tofalse.flush(:boolean) - no margin below, for the last paragraph. Defaults tofalse.- Global attributes are accepted.
Slots
inner_block(required)
@spec styles(map()) :: Phoenix.LiveView.Rendered.t()
The <style> of an e-mail: the 600 px column, the narrow padding and the dark rules. document/1 includes it;
a preview inside another page can place it by itself, with a dark_scope.
Example
<.styles />Attributes
dark_scope(:string) - a CSS selector that turns the dark rules on instead of theprefers-color-schemequery. Defaults tonil.- Global attributes are accepted. goes on the
<style>.
@spec to_html(Phoenix.LiveView.Rendered.t()) :: String.t()
Renders a ~H result (or the output of a function component) to the HTML string a mailer sends.
Example
html = DevactionUI.Email.to_html(~H"<.heading>Hello</.heading>")