├── .gitignore ├── examples ├── colorbox.pdf ├── colorbox.png ├── stickybox.pdf ├── stickybox.png ├── outline-colorbox.pdf ├── outline-colorbox.png ├── slanted-colorbox.pdf ├── slanted-colorbox.png ├── colorbox.typ ├── slanted-colorbox.typ ├── outline-colorbox.typ └── stickybox.typ ├── .vscode └── settings.json ├── tape.svg ├── typst.toml ├── LICENSE ├── background.svg ├── README.md └── lib.typ /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /examples/colorbox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/colorbox.pdf -------------------------------------------------------------------------------- /examples/colorbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/colorbox.png -------------------------------------------------------------------------------- /examples/stickybox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/stickybox.pdf -------------------------------------------------------------------------------- /examples/stickybox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/stickybox.png -------------------------------------------------------------------------------- /examples/outline-colorbox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/outline-colorbox.pdf -------------------------------------------------------------------------------- /examples/outline-colorbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/outline-colorbox.png -------------------------------------------------------------------------------- /examples/slanted-colorbox.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/slanted-colorbox.pdf -------------------------------------------------------------------------------- /examples/slanted-colorbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lkoehl/typst-boxes/HEAD/examples/slanted-colorbox.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typst-lsp.experimentalFormatterMode": "on", 3 | "typst-lsp.exportPdf": "never" 4 | } 5 | -------------------------------------------------------------------------------- /examples/colorbox.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": colorbox 2 | 3 | #set page(paper: "a4", margin: 0.5cm, height: auto) 4 | 5 | #colorbox(title: lorem(5), color: "blue")[ 6 | #lorem(50) 7 | ] -------------------------------------------------------------------------------- /examples/slanted-colorbox.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": slanted-colorbox 2 | 3 | #set page(paper: "a4", margin: 0.5cm, height: auto) 4 | 5 | #slanted-colorbox(title: lorem(5), color: "sand")[ 6 | #lorem(50) 7 | ] -------------------------------------------------------------------------------- /tape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | sticky 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/outline-colorbox.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": outline-colorbox, colorbox 2 | 3 | #set page(paper: "a4", margin: 0.5cm, height: auto) 4 | 5 | #outline-colorbox(title: lorem(5), color: "gray")[ 6 | #lorem(50) 7 | ] 8 | 9 | #outline-colorbox(title: lorem(5), centering: true, color: (fill: green.lighten(70%), stroke: green.darken(40%)))[ 10 | #lorem(50) 11 | ] -------------------------------------------------------------------------------- /typst.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "colorful-boxes" 3 | version = "1.4.3" 4 | repository = "https://github.com/lkoehl/typst-boxes" 5 | entrypoint = "lib.typ" 6 | exclude = ["/examples/*"] 7 | authors = ["Lukas Köhl "] 8 | license = "MIT" 9 | description = "Predefined colorful boxes to spice up your document" 10 | categories = ["components"] 11 | keywords = ["colorbox", "boxes"] 12 | -------------------------------------------------------------------------------- /examples/stickybox.typ: -------------------------------------------------------------------------------- 1 | #import "../lib.typ": stickybox 2 | 3 | #set page(width: auto, margin: 0.5cm, height: auto) 4 | 5 | #grid( 6 | columns: 3 * (auto,), gutter: 3em, 7 | stickybox(width: 5cm, rotation: 5deg)[ 8 | #lorem(20) 9 | ], 10 | 11 | stickybox(width: 5cm, rotation: -5deg, fill: rgb("#ffb6a6"))[ 12 | #lorem(20) 13 | ], 14 | 15 | stickybox( 16 | width: 5cm, 17 | fill: rgb("#d4ffdc"), 18 | tape: false, 19 | )[ 20 | #lorem(20) 21 | ], 22 | ) 23 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 lkoehl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /background.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | background 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Typst Boxes 2 | 3 | **Typst Boxes** is a Typst package for adding colorful, customizable boxes to your documents. 4 | 5 | [➡️ Explore the live example project](https://typst.app/project/rp9q3upfc69bPUCbv0BjzX) 6 | 7 | --- 8 | 9 | ## Table of Contents 10 | 11 | 1. [Features](#features) 12 | 2. [Examples & Usage](#examples--usage) 13 | - [Colorbox](#colorbox) 14 | - [Slanted Colorbox](#slanted-colorbox) 15 | - [Outline Colorbox](#outline-colorbox) 16 | - [Stickybox](#stickybox) 17 | 3. [Contributing](#contributing) 18 | 4. [License](#license) 19 | 20 | --- 21 | 22 | ## Features 23 | 24 | - Predefined color themes: **black**, **red**, **blue**, **green**, **purple**, **gold**, and more 25 | - `slanted-colorbox` for angled headlines 26 | - `outline-colorbox` for simple bordered layouts 27 | - `stickybox` for rotatable notes 28 | - Custom color support via `color` parameter (string or dictionary) 29 | - Disable sticky tape with `tape: false` 30 | 31 | --- 32 | 33 | ## Examples & Usage 34 | 35 | ### Colorbox 36 | 37 | ![colorbox_example](examples/colorbox.png) 38 | 39 | ```typst 40 | #colorbox( 41 | title: lorem(5), 42 | color: "blue", 43 | radius: 2pt, 44 | width: auto, 45 | )[ 46 | #lorem(50) 47 | ] 48 | ``` 49 | 50 | Use other built‑in colors: 51 | `"red"`, `"green"`, `"purple"`, `"gold"`, `"gray"`, `"cyan"`, etc. 52 | 53 | Custom color via dictionary: 54 | 55 | ```typst 56 | #colorbox( 57 | title: "Custom Theme", 58 | color: ( 59 | fill: rgb("#f0f8ff"), 60 | stroke: rgb("#00bfff"), 61 | title: rgb("#002366") 62 | ), 63 | radius: 4pt, 64 | width: auto 65 | )[ 66 | "This box uses a custom color dictionary." 67 | ] 68 | ``` 69 | 70 | --- 71 | 72 | ### Slanted Colorbox 73 | 74 | ![slantedColorbox_example](examples/slanted-colorbox.png) 75 | 76 | ```typst 77 | #slanted-colorbox( 78 | title: lorem(5), 79 | color: "red", 80 | radius: 0pt, 81 | width: auto, 82 | )[ 83 | #lorem(50) 84 | ] 85 | ``` 86 | 87 | --- 88 | 89 | ### Outline Colorbox 90 | 91 | ![outlinebox_example](examples/outline-colorbox.png) 92 | 93 | ```typst 94 | #outline-colorbox( 95 | title: lorem(5), 96 | width: auto, 97 | radius: 2pt, 98 | centering: false, 99 | )[ 100 | #lorem(50) 101 | ] 102 | 103 | #outline-colorbox( 104 | title: lorem(5), 105 | color: "green", 106 | width: auto, 107 | radius: 2pt, 108 | centering: true, 109 | )[ 110 | #lorem(50) 111 | ] 112 | ``` 113 | 114 | Custom colors via dictionary: 115 | 116 | ```typst 117 | #outline-colorbox( 118 | title: "Soft Green", 119 | color: ( 120 | fill: green.lighten(70%), 121 | stroke: green.darken(40%) 122 | ), 123 | width: auto, 124 | radius: 2pt, 125 | centering: false, 126 | )[ 127 | #lorem(20) 128 | ] 129 | ``` 130 | 131 | --- 132 | 133 | ### Stickybox 134 | 135 | ![stickybox](examples/stickybox.png) 136 | 137 | #### Basic 138 | 139 | ```typst 140 | #stickybox( 141 | rotation: 5deg, 142 | width: 5cm, 143 | )[ 144 | #lorem(20) 145 | ] 146 | ``` 147 | 148 | #### Custom Background Color 149 | 150 | ```typst 151 | #stickybox( 152 | width: 5cm, 153 | rotation: -5deg, 154 | fill: rgb("#ffb6a6"), 155 | )[ 156 | #lorem(20) 157 | ] 158 | ``` 159 | 160 | #### Disable Tape 161 | 162 | ```typst 163 | #stickybox( 164 | width: 8cm, 165 | fill: rgb("#33ff57"), 166 | tape: false, 167 | )[ 168 | "No tape here!" 169 | ] 170 | ``` 171 | 172 | --- 173 | 174 | ## Contributing 175 | 176 | We welcome contributions! 177 | 1. Fork this repository 178 | 2. Create a feature branch 179 | 3. Open a pull request with a clear description 180 | 181 | --- 182 | 183 | ## License 184 | 185 | This project is licensed under the **MIT License**. See [LICENSE](LICENSE) for details. 186 | -------------------------------------------------------------------------------- /lib.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/showybox:2.0.3": showybox 2 | 3 | #let box-colors = ( 4 | default: (stroke: luma(70), fill: white, title: white), 5 | red: (stroke: rgb(237, 32, 84), fill: rgb(253, 228, 224), title: white), 6 | green: (stroke: rgb(102, 174, 62), fill: rgb(235, 244, 222), title: white), 7 | blue: (stroke: rgb(29, 144, 208), fill: rgb(232, 246, 253), title: white), 8 | purple: (stroke: rgb(137, 89, 168), fill: rgb(230, 217, 243), title: white), 9 | gray: (stroke: rgb(158, 158, 158), fill: rgb(245, 245, 245), title: white), 10 | cyan: (stroke: rgb(0, 188, 212), fill: rgb(224, 247, 250), title: white), 11 | teal: (stroke: rgb(0, 150, 136), fill: rgb(224, 242, 241), title: white), 12 | indigo: (stroke: rgb(63, 81, 181), fill: rgb(232, 234, 246), title: white), 13 | gold: (stroke: rgb(212, 175, 55), fill: rgb(255, 247, 207), title: white), 14 | lavender: (stroke: rgb(150, 123, 182), fill: rgb(238, 230, 250), title: white), 15 | sand: (stroke: rgb(194, 178, 128), fill: rgb(245, 238, 222), title: white), 16 | ) 17 | 18 | #let colorbox( 19 | title: none, 20 | box-colors: box-colors, 21 | color: "default", 22 | radius: 2pt, 23 | inset: 8pt, 24 | stroke: 2pt, 25 | width: auto, 26 | body, 27 | ) = { 28 | let stroke-color = black 29 | let fill-color = gray.lighten(50%) 30 | let title-color = white 31 | if type(color) == str { 32 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke 33 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill 34 | } else if type(color) == dictionary { 35 | stroke-color = color.at("stroke", default: stroke-color) 36 | fill-color = color.at("fill", default: fill-color) 37 | title-color = color.at("text", default: title-color) 38 | } 39 | if title != none { 40 | return showybox( 41 | title: title, 42 | breakable: true, 43 | frame: ( 44 | title-color: stroke-color, 45 | body-color: fill-color, 46 | border-color: stroke-color, 47 | radius: radius, 48 | thickness: stroke, 49 | body-inset: (top: inset + 4pt, rest: inset), 50 | ), 51 | title-style: ( 52 | color: title-color, 53 | weight: "bold", 54 | boxed-style: ( 55 | anchor: ( 56 | x: left, 57 | y: top, 58 | ), 59 | offset: ( 60 | x: -1em, 61 | ), 62 | radius: (top-left: radius, bottom-right: radius), 63 | ), 64 | ), 65 | body-style: ( 66 | align: left, 67 | color: black, 68 | ), 69 | width: width, 70 | )[ 71 | #body 72 | ] 73 | } else { 74 | return showybox( 75 | breakable: true, 76 | frame: ( 77 | title-color: stroke-color, 78 | body-color: fill-color, 79 | border-color: stroke-color, 80 | radius: radius, 81 | thickness: stroke, 82 | body-inset: inset, 83 | ), 84 | title-style: ( 85 | color: title-color, 86 | weight: "bold", 87 | boxed-style: ( 88 | anchor: ( 89 | x: left, 90 | y: top, 91 | ), 92 | offset: ( 93 | x: -1em, 94 | ), 95 | radius: (radius), 96 | ), 97 | ), 98 | body-style: ( 99 | align: left, 100 | color: black, 101 | ), 102 | width: width, 103 | )[ 104 | #body 105 | ] 106 | } 107 | } 108 | 109 | #let colorbox-rounded( 110 | title: none, 111 | box-colors: box-colors, 112 | color: "default", 113 | radius: 2pt, 114 | inset: 8pt, 115 | stroke: 2pt, 116 | width: auto, 117 | body, 118 | ) = { 119 | let stroke-color = black 120 | let fill-color = gray.lighten(50%) 121 | let title-color = white 122 | if type(color) == str { 123 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke 124 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill 125 | } else if type(color) == dictionary { 126 | stroke-color = color.at("stroke", default: stroke-color) 127 | fill-color = color.at("fill", default: fill-color) 128 | title-color = color.at("text", default: title-color) 129 | } 130 | showybox( 131 | title: title, 132 | breakable: true, 133 | frame: ( 134 | title-color: stroke-color, 135 | body-color: fill-color, 136 | border-color: stroke-color, 137 | radius: radius, 138 | thickness: stroke, 139 | body-inset: inset, 140 | ), 141 | title-style: ( 142 | color: title-color, 143 | weight: "bold", 144 | boxed-style: ( 145 | anchor: ( 146 | x: left, 147 | y: top, 148 | ), 149 | offset: ( 150 | x: -1em, 151 | ), 152 | radius: (radius), 153 | ), 154 | ), 155 | body-style: ( 156 | align: left, 157 | color: black, 158 | ), 159 | width: width, 160 | )[ 161 | #body 162 | ] 163 | } 164 | 165 | #let slanted-colorbox( 166 | title: "Title", 167 | box-colors: box-colors, 168 | color: "default", 169 | radius: 2pt, 170 | stroke: 2pt, 171 | inset: 8pt, 172 | width: auto, 173 | body, 174 | ) = { 175 | let stroke-color = black 176 | let fill-color = gray.lighten(50%) 177 | let title-color = white 178 | if type(color) == str { 179 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke 180 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill 181 | } else if type(color) == dictionary { 182 | stroke-color = color.at("stroke", default: stroke-color) 183 | fill-color = color.at("fill", default: fill-color) 184 | title-color = color.at("text", default: title-color) 185 | } 186 | showybox( 187 | breakable: true, 188 | frame: ( 189 | body-color: fill-color, 190 | border-color: stroke-color, 191 | radius: radius, 192 | thickness: stroke, 193 | body-inset: 0pt, 194 | ), 195 | width: width, 196 | )[ 197 | #context { 198 | let size = measure(title) 199 | let inset = 8pt 200 | polygon( 201 | fill: stroke-color, 202 | (0pt, 0pt), 203 | (0pt, size.height + (2 * inset)), 204 | (size.width * 1.11 + (2 * inset), size.height + (2 * inset)), 205 | (size.width * 1.11 + (2 * inset) + 6pt, 0cm), 206 | ) 207 | } 208 | #place(left + top, dx: 10pt, dy: 7pt)[ 209 | #text(fill: white, weight: "bold")[#title] 210 | ] 211 | #block(inset: (top: inset - 10pt, rest: inset))[ 212 | #body 213 | ] 214 | ] 215 | } 216 | 217 | 218 | #let outline-colorbox( 219 | title: "Title", 220 | box-colors: box-colors, 221 | color: "default", 222 | width: 100%, 223 | radius: 2pt, 224 | stroke: 2pt, 225 | inset: 8pt, 226 | centering: false, 227 | body, 228 | ) = { 229 | let stroke-color = black 230 | let fill-color = gray.lighten(50%) 231 | let title-color = white 232 | if type(color) == str { 233 | stroke-color = box-colors.at(color, default:(stroke: black)).stroke 234 | fill-color = box-colors.at(color, default:(fill: gray.lighten(50%))).fill 235 | } else if type(color) == dictionary { 236 | stroke-color = color.at("stroke", default: stroke-color) 237 | fill-color = color.at("fill", default: fill-color) 238 | title-color = color.at("text", default: title-color) 239 | } 240 | return showybox( 241 | title: text(fill: white, weight: "bold")[#title], 242 | breakable: true, 243 | frame: ( 244 | title-color: stroke-color, 245 | body-color: fill-color, 246 | border-color: stroke-color, 247 | radius: radius, 248 | thickness: stroke, 249 | body-inset: inset, 250 | ), 251 | title-style: ( 252 | color: title-color, 253 | weight: "bold", 254 | boxed-style: ( 255 | anchor: ( 256 | x: if centering { center } else { left }, 257 | y: horizon, 258 | ), 259 | offset: ( 260 | x: if centering { 0em } else { 1em }, 261 | ), 262 | radius: (radius), 263 | ), 264 | ), 265 | width: width, 266 | )[ 267 | #body 268 | ] 269 | } 270 | 271 | #let stickybox(rotation: 0deg, width: 100%, fill: rgb(255, 240, 172), tape: true, body) = { 272 | return rotate(rotation)[ 273 | #block(width: width)[ 274 | #layout(size => { 275 | let height = measure(image("background.svg", width: size.width)).height 276 | place( 277 | bottom + center, 278 | dy: 0.6 * height, 279 | )[ 280 | #image("background.svg", width: size.width) 281 | ] 282 | box( 283 | fill: fill, 284 | width: width, 285 | inset: (top: if tape { 12pt } else { 8pt }, x: 8pt, bottom: 8pt), 286 | )[ 287 | #body 288 | #if tape { 289 | place( 290 | top + center, 291 | dy: -2mm - 12pt, 292 | )[ 293 | #image( 294 | "tape.svg", 295 | width: if type(width) == ratio { calc.clamp(width * 0.35cm / 1cm, 1, 4) * 1cm } else { 296 | calc.clamp(width * 0.35 / 1cm, 1, 4) * 1cm 297 | }, 298 | height: 4mm, 299 | ) 300 | ] 301 | } 302 | ] 303 | }) 304 | ] 305 | ] 306 | } 307 | --------------------------------------------------------------------------------