Emerge.UI.Border (Emerge v0.5.1)

Copy Markdown View Source

Border styling attributes.

Layout

Border width participates in layout in Emerge.

Content and child layout are inset by the sum of padding and border width on each side. For an element with padding(10) and Border.width(5), content starts 15px in from each edge.

For fixed-size elements, the outer size stays the same and border width reduces the inner content area. For content-sized elements, the element grows by the border widths.

Border width can affect layout even when no border is painted. Layout uses border_width, while painting also requires border_color.

Radius

Border width defaults to 0, so Border.rounded(8) rounds the element corners but does not draw a visible border by itself.

Rounded corners affect backgrounds and clipping, and also shape the border when width and color are present.

Shadows

shadow/1 and glow/2 paint the complete box-shaped shadow behind the element's background and content, including behind its text. Transparent areas reveal the shadow; opaque backgrounds cover it. Unlike CSS box-shadow, the casting box's interior is not cut out of the shadow.

Shadows are decorative and do not affect layout.

Parent padding does not clip descendant rendering, so outer shadows can bleed into a parent's padding.

Scroll containers clip outer shadows only on active scroll axes: scroll_y clips top and bottom, scroll_x clips left and right. If both axes scroll, the full scrollport clips the shadow, including rounded corners.

inner_shadow/1 renders inside the element and does not bleed outside it.

Inline paragraph wrappers

All border styles, independent edge widths/corner radii, shadows and glow work on el(attrs, text(...)) inside a paragraph. Each wrapped line segment gets a complete box with its own edges, corners, padding and shadow stack (clone-on-wrap). Words on the same line share the box; adjacent wrappers remain separate.

Border width and padding reserve space and can change wrapping and line height. Shadows and glow remain decorative, including when no visible border is set. Outer shadows can bleed between lines and into padding; the existing active-axis scroll clips still apply. Inset shadows are painted inside the rounded box, behind the text. Explicit Background.color/1 fills paint over outer shadows and under inset shadows/text: opaque colors hide the interior shadow while transparency reveals it. No implicit background is painted. Borders are painted after the text.

Background and border gradients span each line segment's outer box. Shadow gradients use that same casting box before offset, spread and blur. Both restart at a wrapped continuation, not at each word. Text gradients still span the paragraph.

This does not add inline background images, nested wrapper extraction or a new multiline hit-testing model. Floating elements retain ordinary box layout.

el(
  [width(fill()), height(fill()), padding(24), Background.color(color(:slate, 900))],
  paragraph([width(fill()), Font.size(24), Font.color(:white), center_x()], [
    text("Decorations follow "),
    el(
      [
        padding(3),
        Border.width_each(1, 2, 3, 1),
        Border.rounded_each(8, 0, 8, 0),
        Border.dashed(),
        Border.color(gradient([color(:cyan, 300), color(:blue, 500)])),
        Border.glow(color_rgba(43, 145, 203, 0.4), 2),
        Border.inner_shadow(size: 1, blur: 2, color: color_rgba(43, 145, 203, 0.4))
      ],
      text("inline wrappers across several wrapped lines")
    ),
    text(" without recoloring the text.")
  ])
)
Wrapped inline borders, shadow and glow

Examples

Rounded corners shape the element even before a visible border is added. Adding width and color then paints the border on that same outline.

row(
  [
    height(fill()),
    spacing(16),
    padding(12),
    Background.color(color(:slate, 900)),
    Border.rounded(12)
  ],
  [
    el(
      [
        width(px(148)),
        padding(12),
        Background.color(color(:slate, 50)),
        Font.color(color(:slate, 800)),
        Border.rounded(14)
      ],
      text("Rounded only")
    ),
    el(
      [
        width(px(148)),
        padding(12),
        Background.color(color(:slate, 50)),
        Border.rounded(14),
        Border.width(3),
        Border.color(color(:sky, 400)),
        Font.color(color(:slate, 800))
      ],
      text("Width + color")
    )
  ]
)
Rendered border radius and width example

Shadows are easiest to understand side by side: outer shadow bleeds outward, glow is a zero-offset shadow, and inner shadow stays inside the element.

el(
  [
    width(fill()),
    height(fill()),
    padding(12),
    Background.color(color(:slate, 900)),
    Border.rounded(12),
    center_y()
  ],
  row(
    [
      width(fill()),
      padding(16),
      spacing(16),
      Background.color(color(:slate, 500)),
      Border.rounded(14)
    ],
    [
      el(
        [
          width(px(120)),
          height(px(58)),
          padding(12),
          Background.color(color(:slate, 50)),
          Border.rounded(12),
          Border.shadow(offset: {0, 16}, size: 8, blur: 24, color: color_rgba(15, 23, 42, 0.7)),
          center_y(),
          Font.color(color(:slate, 800))
        ],
        text("Shadow")
      ),
      el(
        [
          width(px(120)),
          height(px(58)),
          padding(12),
          Background.color(color(:slate, 800)),
          Border.rounded(12),
          Border.glow(color_rgba(56, 189, 248, 0.55), 3),
          center_y(),
          Font.color(color(:slate, 50))
        ],
        text("Glow")
      ),
      el(
        [
          width(px(120)),
          height(px(58)),
          padding(12),
          Background.color(color(:slate, 100)),
          Border.rounded(12),
          Border.width(1),
          Border.color(color(:slate, 300)),
          Border.inner_shadow(
            offset: {0, 0},
            size: 8,
            blur: 18,
            color: color_rgba(56, 189, 248, 0.9)
          ),
          center_y(),
          Font.color(color(:slate, 800))
        ],
        text("Inner")
      )
    ]
  )
)
Rendered border shadow comparison

Summary

Functions

Set border color

Dashed border style

Dotted border style

Add a uniform glow around the element.

Add an inner shadow (inset box shadow).

Round the element corners. This does not draw a visible border unless width and color are also set.

Round each element corner individually. This does not draw a visible border unless width and color are also set.

Add a box-shaped outer shadow, including behind the element's content.

Solid border style (default)

Set border width. Border width participates in layout and reduces the content area.

Set per-edge border widths (top, right, bottom, left). These widths participate in layout on their respective sides.

Types

color_value()

@type color_value() :: Emerge.UI.Color.color() | Emerge.UI.Color.t()

radius()

@type radius() :: number() | {number(), number(), number(), number()}

shadow_options()

@type shadow_options() :: keyword()

shadow_value()

@type shadow_value() :: %{
  offset_x: number(),
  offset_y: number(),
  size: number(),
  blur: number(),
  color: color_value(),
  inset: boolean()
}

style()

@type style() :: :solid | :dashed | :dotted

t()

@type t() ::
  {:border_radius, radius()}
  | {:border_width, width_value()}
  | {:border_color, color_value()}
  | {:border_style, style()}
  | {:box_shadow, shadow_value()}

width_value()

@type width_value() :: number() | {number(), number(), number(), number()}

Functions

color(c)

@spec color(color_value()) :: {:border_color, color_value()}

Set border color

dashed()

@spec dashed() :: {:border_style, :dashed}

Dashed border style

dotted()

@spec dotted() :: {:border_style, :dotted}

Dotted border style

glow(color, size)

@spec glow(color_value(), number()) :: {:box_shadow, shadow_value()}

Add a uniform glow around the element.

Sugar for shadow/1 with zero offset.

Decorative only. glow/2 follows the same bleed and scroll-axis clipping behavior as outer shadows.

inner_shadow(opts \\ [])

@spec inner_shadow(shadow_options()) :: {:box_shadow, shadow_value()}

Add an inner shadow (inset box shadow).

Same options as shadow/1 but rendered inside the element.

Decorative only. Inner shadows do not affect layout and stay inside the element.

rounded(r)

@spec rounded(radius()) :: {:border_radius, radius()}

Round the element corners. This does not draw a visible border unless width and color are also set.

rounded_each(tl, tr, br, bl)

@spec rounded_each(number(), number(), number(), number()) ::
  {:border_radius, radius()}

Round each element corner individually. This does not draw a visible border unless width and color are also set.

shadow(opts \\ [])

@spec shadow(shadow_options()) :: {:box_shadow, shadow_value()}

Add a box-shaped outer shadow, including behind the element's content.

Transparent backgrounds reveal the shadow inside the box; opaque backgrounds painted over it provide occlusion. This is not a glyph-shaped text shadow.

Decorative only. Outer shadows do not affect layout.

Parent padding does not clip descendant rendering, so outer shadows can bleed into a parent's padding.

Scroll containers clip outer shadows only on active scroll axes.

Options:

  • :offset - {x, y} offset (default {0, 0})
  • :size - spread size in pixels (default 0)
  • :blur - blur radius in pixels (default 10)
  • :color - shadow color (default :black)

solid()

@spec solid() :: {:border_style, :solid}

Solid border style (default)

width(w)

@spec width(width_value()) :: {:border_width, width_value()}

Set border width. Border width participates in layout and reduces the content area.

width_each(top, right, bottom, left)

@spec width_each(number(), number(), number(), number()) ::
  {:border_width, width_value()}

Set per-edge border widths (top, right, bottom, left). These widths participate in layout on their respective sides.