Skip to content

Recipes

Adding a New Module

  1. Create a new file, for example profile_en/volunteering.typ
  2. Add your imports and your content (sections, entries, and more)
  3. In cv.typ, add "volunteering" to the import-modules call

Switching Profiles at Compile Time

typst compile cv.typ --input profile=fr

The profile input selects the profile_<name>/ directory to load. For a non-Latin script (Chinese, Japanese, Korean, Russian, Arabic, and more), configure the typography explicitly. Set the font fallback chain in [layout.fonts], the title highlight in [layout.section], and the name in [personal] display_name. For a complete example, see template/profile_zh/metadata.toml.

Adding a New Profile

Each profile is self-contained. One profile_<name>/metadata.toml holds the complete CV configuration for that variant. To add a new profile, for example a Swedish CV, do these steps:

  1. Copy an existing profile directory:
    cp -r template/profile_en template/profile_swe
    
  2. Edit profile_swe/metadata.toml. Change header_quote, cv_footer, letter_footer, and each other field that is different from English.
  3. Edit the .typ modules under profile_swe/ for Swedish content.
  4. Compile with typst compile cv.typ --input profile=swe.

v4 has no shared root metadata and no merge mechanism. One profile is one complete CV configuration. To start a new profile, copy an existing one.

Profile ≠ language

The profile name is only a directory suffix. It has no meaning for the package. For example, you can keep profile_us/ and profile_uk/ next to profile_zh/. The first two hold English text with different locations, phone numbers, and layouts. The third holds Chinese text with Heiti SC.

The metadata.toml of each profile is complete on its own. Select the directory names that are correct for your variants.

Sharing configuration across profiles

The package has no merge mechanism for shared fields, such as a GitHub username or the layout colors. If you keep many profiles, these options are available to you:

  • A small Python or shell preprocessor. It reads a canonical base and the overrides of each profile, then writes the profile metadata.toml files at build time.
  • typstyle-friendly manual edits. For 2 or 3 profiles, manual synchronization is usually sufficient.
  • Symlinks or git rerere to keep the selected fields synchronized.

The package keeps one profile in one file, so you can select your own tools.

Skills with Inline Separators

Use #h-bar() to separate the skill items in cv-skill:

#cv-skill(
  type: [Tech Stack],
  info: [Python #h-bar() SQL #h-bar() Tableau #h-bar() AWS],
)

cv-honor is compact. It has date, title, issuer, url, and location, but it has no free-form description field. Select the component that shows the data you need.

For a clickable verification link only, pass the verification URL as url. The title becomes a link. Put short status text in location:

#cv-honor(
  date: [2025],
  title: [AWS Certified Solutions Architect],
  issuer: [Amazon Web Services],
  url: "https://www.credly.com/badges/<your-badge-id>",
  location: [Verified],
)

For a credential ID, an expiry date, or more than one link, use cv-entry. Its description field accepts any content. This component uses more space on the page, but it gives you full control of the format:

#cv-entry(
  title: [AWS Certified Solutions Architect],
  society: [Amazon Web Services],
  date: [2025 -- 2028],
  location: [Verified],
  description: list(
    [Credential ID: ABC-123-XYZ],
    [#link("https://verify.example.com")[Verify online]],
  ),
)

You can use the two components together in one Certificates section. For each entry, select cv-honor for the compact one-line layout, or cv-entry for the larger description block.

Adding a Profile Photo

You pass the profile photo as an argument to cv() in your cv.typ. You do not set it in metadata.toml:

#show: cv.with(
  metadata,
  profile-photo: image("assets/avatar.png", alt: "Profile photo"),
)

Control the shape with profile_photo_radius in [layout.header]:

  • "50%" — circle (default)
  • "0%" — square
  • "10%" — rounded corners

To hide the photo, set display_profile_photo = false in [layout.header].

Custom Contact Icon with Image

To add a custom contact entry with an image icon in place of a Font Awesome icon, do these steps:

  1. Define the entry in metadata.toml with an awesomeIcon fallback:

    [personal.info.custom-1]
    awesomeIcon = "graduation-cap"
    text = "PhD in Data Science"
    link = "https://example.com"
    
  2. Pass the image in cv.typ with the custom-icons parameter. The key must agree with custom-1:

    #show: cv.with(
      metadata,
      profile-photo: image("assets/avatar.png", alt: "Profile photo"),
      custom-icons: (
        "custom-1": image("assets/my-icon.png"),
      ),
    )
    

If you give a custom-icons entry, it has priority over the awesomeIcon value from the TOML file.

Custom Header Info

The default header makes a linked contact item from each [personal.info] entry. It adds the icons and puts h-bar() between the items automatically. You can also control the separators, the line breaks, and the accent color of each span. To do this, pass your own content in header-info:

#import "@preview/brilliant-cv:4.1.1": cv, h-bar

#let info = metadata.personal.info

#show: cv.with(
  metadata,
  profile-photo: image("assets/avatar.png", alt: "Profile photo"),
  header-info: [
    #link("mailto:" + info.email)[#info.email]
    #h-bar()
    #text(fill: black)[Berlin, Germany]
    #linebreak()
    #text(fill: rgb("#2E7D32"))[Available for remote work]
  ],
)

Your content inherits the default font size and accent color of the header info. To override these defaults, style each span explicitly. Your content also replaces the automatic rendering of [personal.info]. Add the icons and the links that you want directly in the content. custom-icons applies only to the default auto renderer.

You can use a function to make your template clearer. Call the function first, then pass its result. The API accepts content, not a renderer callback:

#let render-info(info) = [#info.email #h-bar() #info.location]

#show: cv.with(
  metadata,
  header-info: render-info(metadata.personal.info),
)

To remove the contact row, pass header-info: none. The name, the optional quote, the photo, and the rest of the layout do not change.

Color Customization

Preset Colors

Set awesome_color in [layout] to one of these presets:

Name Hex
skyblue #0395DE
red #DC3522
nephritis #27AE60
concrete #95A5A6
darknight #131A28
[layout]
awesome_color = "nephritis"

Custom Hex Color

You can also set any hex color string directly:

[layout]
awesome_color = "#1E90FF"

Restyle One Part of the Text

[layout.parts.<name>] changes the text style of one named part in all of the CV and the cover letter. For example, this makes the bold first line of each entry larger and red:

[layout.parts.entry-primary]
size = "13pt"
weight = "regular"
fill = "red"

entry-primary restyled

Each part accepts these properties. A property that you do not set keeps the package default.

Property Value
size A length in pt, mm, cm, in, or em, for example "11pt"
weight A weight name, for example "regular" or "bold", or a number from 100 to 900
style "normal", "italic", or "oblique"
fill A preset color name (see Color Customization) or "#rrggbb"
font A font name, or a list of font names

font replaces the full font fallback list for that part. If the part contains CJK text, include a CJK font in the list.

The parts have names that tell their position, not their meaning. For example, entry-primary is the bold first line of an entry. This line is the society, or the title when display_entry_society_first = false.

Where Parts
CV header name-first, name-last, header-info, header-quote
Sections section-title
Entries entry-primary, entry-primary-aside, entry-secondary, entry-secondary-aside, entry-description, entry-tag
Skills skill-type, skill-info, skill-tag
Honors honor-date, honor-title, honor-issuer, honor-location
Publications publication
Cover letter letter-sender-name, letter-sender-address, letter-recipient-name, letter-recipient-address, letter-date, letter-subject
CV and cover letter footer

An unknown part or property stops the compilation with an error that names the correct values. The schema in your template also shows these errors in the editor.

A fill on section-title replaces the highlight colors of the title. The color argument of one cv-section, cv-entry, or cv-honor call has priority over fill for that call, on the parts that the argument colors: section-title, entry-primary-aside, entry-secondary, and honor-location.

Use a show rule (experimental). Each part also has the label <bcv-<name>>. A show-set rule in cv.typ has priority over the package defaults and over [layout.parts]:

#show <bcv-entry-primary>: set text(size: 13pt, fill: rgb("#27AE60"))

Use [layout.parts] for most changes. A show rule can use all text properties, but the labels can change in a minor release.

Cover Letter with Signature

There are two ways to add a signature to a cover letter.

Keep the closing, the signature, and your name together (recommended). Put them in an unbreakable block at the end of the letter body. If the block does not fit on the page, the full block moves to the next page:

#import "@preview/brilliant-cv:4.1.1": letter

#let metadata = toml("profile_en/metadata.toml")

#show: letter.with(
  metadata,
  recipient-name: "Acme Analytics",
  recipient-address: "456 Business Ave, City, State 67890",
  subject: "Application for Data Analyst Position",
)

Dear Hiring Manager,

// Your letter content here...

#block(breakable: false)[
  Sincerely,

  #image("assets/signature.png", width: 25%, alt: "Signature")

  John Doe
]

You control the order and the alignment of the lines in the block. For example, to put the signature on the right, use #align(right, image("assets/signature.png", width: 25%, alt: "Signature")).

Use the signature parameter. letter() puts the image on the right, below the letter body:

#show: letter.with(
  metadata,
  subject: "Application for Data Analyst Position",
  signature: image("assets/signature.png", alt: "Signature"),
)

The closing lines that you write in the body do not move with this image. Use this option only when the body leaves room for the image. To omit the signature image, leave signature as "". This is the default value.

Check the Layout Without Rendering (Experimental)

Scripts and AI agents can read the layout as JSON, without rendering images. Add --input brilliant-cv-query=1 to a typst eval command (Typst 0.15 and later):

typst eval 'query(<brilliant-cv>).map(it => it.value)' --in cv.typ --input brilliant-cv-query=1

The result has one object for each section, entry, skill, honor, and publication list, in document order. Each object has a kind and a page, which is the page on which the element ends. The last object has the kind document and the total number of pages in pages:

[
  {"kind": "section", "page": 1, "title": "Education"},
  {"kind": "entry", "page": 1, "title": "Master of Data Science", "society": "Aurora State University", "date": "2015 - 2017", "location": "Aurora, WA"},
  {"kind": "document", "page": 2, "pages": 2}
]

On Typst 0.14, which has no typst eval, use typst query. Typst 0.15 still accepts it, but it prints a deprecation warning on stderr:

typst query cv.typ '<brilliant-cv>' --field value --input brilliant-cv-query=1

Use it, for example, to find the entries that go to page 2, or to make sure that a cover letter has one page. Without the input, the package emits nothing, and the input does not change the layout. This function is experimental: the field names can change in a minor release. The cv() entry in the API Reference lists all the fields.

CI/CD with GitHub Actions

This is a minimal workflow that compiles your CV on each push:

name: Build CV

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Typst
        uses: typst-community/setup-typst@v4

      - name: Compile CV
        run: typst compile cv.typ cv.pdf

      - name: Upload PDF
        uses: actions/upload-artifact@v4
        with:
          name: cv
          path: cv.pdf

Tip

If your CV uses custom fonts, add a step that installs them before the compile step. For font problems, see the Troubleshooting page.