Universe

Box shadows for Typst.

Installation

Add the package to your Typst document:

#import "@preview/shadowed:0.4.0": shadow

Quick Start

#import "@preview/shadowed:0.4.0": shadow

#set par(justify: true)

#shadow(blur: 8pt, fill: rgb(89, 85, 101, 25%), radius: 4pt)[
  #block(fill: white, radius: 4pt, inset: 12pt)[
    #lorem(50)
  ]
]

Minimal usage example

Parameters

/// A box shadow.
/// -> content
#let shadow(
  /// Whether to draw the shadow inside the box instead of outside.
  ///
  /// -> bool
  inset: false,
  /// The horizontal offset.
  /// -> length
  dx: 0pt,
  /// The vertical offset.
  /// -> length
  dy: 0pt,
  /// How strong to blur the shadow.
  /// 
  /// Must be equal to or greater than 0pt.
  /// 
  /// -> length
  blur: 0pt,
  /// How far to spread the shadow.
  ///
  /// Can be either:
  /// - A length for a uniform spread.
  ///
  /// - A dictionary: With a dictionary, the spread for each side can be set
  ///   individually.
  ///   The dictionary can contain the following keys in order of precedence:
  ///   - top: The spread of the top side.
  ///   - right: The spread of the right side.
  ///   - bottom: The spread of the bottom side.
  ///   - left: The spread of the left side.
  ///   - x: The spread of the left and right sides.
  ///   - y: The spread of the top and bottom sides.
  ///   - rest: The spread for all sides except those for which the dictionary
  ///     explicitly sets a size.
  ///
  /// Negative values contract the shadow on the respective side.
  ///
  /// -> length | dictionary
  spread: 0pt,
  /// How to fill the shadow.
  /// 
  /// Currently only supports linear or radial gradients.
  /// 
  /// -> color | gradient | none
  fill: black,
  /// How much to round the shadow's corners.
  ///
  /// Can be either:
  /// - A relative length for a uniform corner radius,
  ///   relative to the minimum of the width and height divided by two.
  ///
  /// - A dictionary: With a dictionary, the stroke for each side can be set
  ///   individually.
  ///   The dictionary can contain the following keys in order of precedence:
  ///   - top-left: The top-left corner radius.
  ///   - top-right: The top-right corner radius.
  ///   - bottom-right: The bottom-right corner radius.
  ///   - bottom-left: The bottom-left corner radius.
  ///   - left: The top-left and bottom-left corner radii.
  ///   - top: The top-left and top-right corner radii.
  ///   - right: The top-right and bottom-right corner radii.
  ///   - bottom: The bottom-left and bottom-right corner radii.
  ///   - rest: The radii for all corners except those for which the dictionary
  ///     explicitly sets a size.
  ///
  /// -> relative | dictionary
  radius: 0pt,
  /// The content to place in front of the shadow.
  /// -> content
  body,
) = { /* ... */ }

Examples

Inner Shadow

Set inset to true to draw the shadow inside the box:

#shadow(inset: true, spread: 2pt, blur: 4pt)[
  #block(inset: 4pt)[
    #text(size: 24pt)[
      This box has an inner shadow
    ]
  ]
]

Inner shadow example

Multiple Shadows

You can apply multiple shadows to the same content by stacking them:

#shadow(spread: 2pt, blur: 4pt, fill: red)[
  #shadow(inset: true, spread: 2pt, blur: 4pt, fill: blue)[
    #block(inset: 4pt, fill: white)[
      #text(size: 24pt)[
        This box has an inner and outer shadow
      ]
    ]
  ]
]

Multiple shadows example

Offset

Control the shadow position with dx and dy parameters:

// Shadow offset to the bottom-right
#shadow(dx: 3pt, dy: 3pt, blur: 4pt)[
  #block(width: 100pt, height: 100pt, fill: white)
]

// Shadow offset to the top-left
#shadow(dx: -3pt, dy: -3pt, blur: 4pt)[
  #block(width: 100pt, height: 100pt, fill: white)
]

Alternatively the offsets can be achieved by using an asymmetric spread dictionary instead of dx/dy:

// Shadow offset to the bottom-right
#shadow(blur: 4pt, spread: (top: -1pt, left: -1pt, right: 1pt, bottom: 1pt))[
  #block(width: 100pt, height: 100pt, fill: white)
]

// Shadow offset to the top-left
#shadow(blur: 4pt, spread: (top: 1pt, left: 1pt, right: -1pt, bottom: -1pt))[
  #block(width: 100pt, height: 100pt, fill: white)
]

Offset example

Spread

The spread parameter controls how much the shadow expands or contracts:

// Positive spread makes the shadow larger
#shadow(blur: 4pt, spread: 2pt)[
  #block(width: 100pt, height: 100pt, fill: white)
]

// Negative spread makes the shadow smaller
#shadow(blur: 4pt, spread: -2pt)[
  #block(width: 100pt, height: 100pt, fill: white)
]

The spread for each side can be controlled individually with a dictionary:

// Spread only downwards
#shadow(blur: 4pt, spread: (bottom: 10pt))[
  #block(width: 100pt, height: 100pt, fill: white)
]

// Spread to the right
#shadow(blur: 4pt, spread: (right: 10pt))[
  #block(width: 100pt, height: 100pt, fill: white)
]

// Asymmetric spread
#shadow(blur: 4pt, spread: (top: 2pt, right: 8pt, bottom: 10pt, left: -4pt))[
  #block(width: 100pt, height: 100pt, fill: white)
]

Spread example

Layout

By default shadow does not affect the layout and just draws the shadow behind its content. This can lead to overlapping elements. To prevent this a pad of blur + spread can be applied.

#let blur = 4pt
#let spread = 2pt

// Padding has to consist of the blur + the spread.
#pad(blur + spread)[
  #shadow(blur: blur, spread: spread)[
    #block(height: 120pt, width: 120pt, fill: white)
  ]
]

For comparison, here are two shadows where one affects the layout while the other does not:

Layout example

Fill

The fill of a shadow can consist of nothing, a solid color or a gradient. Currently only linear and radial gradients are supported.

// Solid color
#shadow(blur: 3pt, fill: color.teal)[
  #block(width: 120pt, height: 120pt)
]

// Linear gradient
#shadow(blur: 3pt, fill: gradient.linear(..color.map.rainbow, angle: 45deg))[
  #block(width: 120pt, height: 120pt)
]

// Radial gradient
#shadow(blur: 3pt, fill: gradient.radial(..color.map.plasma, center: (40%, 40%)))[
  #block(width: 120pt, height: 120pt)
]

Fill example

Custom Radius

You can set individual corner radii using a dictionary:

#let radius = (
  top-left: 0pt,
  top-right: 8pt,
  bottom-right: 0pt,
  bottom-left: 8pt,
)

#shadow(
  blur: 4pt,
  radius: radius,
)[
  #block(
    width: 100pt,
    height: 100pt,
    fill: white,
    radius: radius,
  )
]

Custom radius example

License

shadowed is available under the MIT OR Apache-2.0 license. See LICENSE-MIT and LICENSE-APACHE for details.

Credits

This project was inspired by Harbinger.