$yaml-resume

v1.0.0 React 19 · TypeScript · Vite GPL-3.0-or-later YAML-driven

A single-file YAML resume that renders into a clean, filterable web page. Recruiters can click a skill and instantly see what you know, how long you've used it, where, and when.

What you know
Skills attached to every job, project, and degree
How you know it
Each skill traces back to the work that produced it
What you used it on
Filter to surface only matching responsibilities
When you used it
Years used + last touched, computed live
i

The standout feature is the skill filter: click any tag and the resume dims non-matching entries while surfacing per-skill stats (years used · projects · year range).

01 Features

Filterable skill tags: click any tag to scope the resume
YAML data source: one file, no DB, no CMS
Five built-in themes: dark, light, gruvbox, solarized, matrix
Responsive: readable on phone, tablet, desktop
Pluggable view system: swap or write your own layout
Print-friendly: exports clean to PDF
npm scaffolding: npm create yaml-resume gets you started in seconds
Git Pages publish: one-command deploy to Codeberg or GitHub Pages

02 Project structure

After scaffolding, most edits happen in public/ and src/. Views and themes each have a registry file that keeps everything in sync.

my-resume-site/ // created by npm create yaml-resume ├── public/ │ └── resume.yaml // your resume data: edit this ├── src/ │ ├── components/ // Experience, Projects, Filters, … │ ├── contexts/ // SkillFilter, SkillKey, Theme providers │ ├── hooks/ │ │ └── useSkillStats.ts // computes years · uses · range │ ├── themes/ │ │ ├── registry.ts // add themes here │ │ ├── dark.css │ │ ├── light.css │ │ └── │ ├── views/ │ │ ├── registry.ts // add views here │ │ └── terminal.tsx // built-in view │ ├── App.tsx │ ├── Resume.tsx // active view router (set by VITE_VIEW) │ ├── types.ts // authoritative YAML shape │ ├── style.css │ └── theme.ts ├── .env // VITE_VIEW, VITE_THEME_DARK, VITE_THEME_LIGHT, … ├── index.html ├── package.json └── vite.config.ts

03 Requirements

  • Node.js ≥ 20.x (anything that runs Vite 8)
  • npm, pnpm, or yarn (examples use npm)
  • git: required for the bundled publishing script
  • A text editor that doesn't fight YAML's whitespace

04 Install & run

Scaffold a new project with one command:

$ npm create yaml-resume my-resume-site
  ✔ Project created at ./my-resume-site

$ cd my-resume-site
$ npm install
$ npm run dev
  ➜  Local:   http://localhost:5173/

Then open http://localhost:5173/; edits to public/resume.yaml hot-reload.

Alternative: clone the repo

If you prefer to work directly from source:

$ git clone https://codeberg.org/QueSeraSara/resume
$ cd resume
$ cp .env.example .env
$ npm install
$ npm run dev

05 The YAML file

Everything you see on the page comes from one file. The full schema lives in src/types.ts; the short version is below.

description: "Software engineer focused on…"
name:        "Kostya Arminleg"
title:       "Software Engineer"
location:    "Example City, USA"

links:
  github:   "https://github.com/…"
  linkedin: "https://linkedin.com/in/…"

# Pretty-name your skill slugs.  Slugs are what you tag with;
# titles are what's rendered.
skillkey:
  react:      "React"
  typescript: "TypeScript"
  postgresql: "PostgreSQL"

experience:
  - name:        "Example Co."
    title:       "Senior Engineer"
    start:       "2021/07/12"
    end:         "2025/02/28"
    skills:      [agile, linear]
    responsibilities:
      - description: "Led the dashboard rewrite."
        skills:      [react, typescript, vite]

Same shape for projects and education. Any field marked hidden: true is skipped at render time, so you can keep older roles in the file without showing them.

FieldTypeNotes
namestringJob / school / project title
start / endYYYY/MM/DDDrives the "years used" math
skillsstring[]References slugs from skillkey
responsibilitiesobject[]Each one carries its own skills
hiddenbooleanHide without deleting

06 Environment vars

Copy .env.example to .env and adjust. The Vite-prefixed vars get baked into the build; the others are read by the publish script.

VITE_RESUME_FILE=/resume.yaml        # path to the YAML served at runtime
VITE_VIEW=terminal                 # which view to render (src/views/)
VITE_THEME_DARK=dark               # dark theme (dark | gruvbox | matrix)
VITE_THEME_LIGHT=light             # light theme (light | solarized)
VITE_DEFAULT_TITLE="My Resume"
VITE_DATE_FORMAT_MONTH_YEAR="MMM YYYY"
VITE_DATE_FORMAT_YEAR="YYYY"
VITE_ICON_SIZE=32

# publish.mjs reads these (not needed in the scaffolded project)
RESUME_BUILD_DIR="dist"
RESUME_GIT_PAGES_BRANCH="pages"
RESUME_CUSTOM_DOMAINS="resume.example.com"

07 Customize

  1. View: set VITE_VIEW in .env, then rebuild. To write a custom view, add a .tsx file to src/views/, register it in registry.ts, and add it to the VIEW_MAP in src/Resume.tsx.
  2. Themes: set VITE_THEME_DARK and VITE_THEME_LIGHT in .env. To add a theme, create src/themes/<name>.css, import it in style.css, and add an entry to src/themes/registry.ts.
  3. Colors & typography: edit the CSS variables inside your theme file (src/themes/<name>.css).
  4. Skill-stat math: src/hooks/useSkillStats.ts derives years and project counts from your YAML; tweak the heuristic there.
i

Tip: the only file you must edit is public/resume.yaml. Everything else has sensible defaults.

08 Publishing

The repo ships a one-command publish script that builds the site and pushes it to a separate pages branch. It works out of the box with Codeberg Pages or GitHub Pages.

$ npm run publish

The script (scripts/publish.mjs) does, in order:

  1. Clean any prior worktree and the build branch
  2. Create a fresh worktree on an orphan pages branch
  3. Run vite build into a temp dir
  4. Empty the worktree (preserving .git) and copy the build in
  5. Drop a .domains file if you've set RESUME_CUSTOM_DOMAINS
  6. Commit and force-push to origin/pages
  7. Tear down the worktree and temp dir
!

Heads up: the publish script force-pushes the pages branch and clears $RESUME_BUILD_DIR before building. Don't keep anything you care about in there.

09 npm scripts

CommandWhat it does
npm run devVite dev server with hot reload
npm run buildType-check (tsc -b) then build for production
npm run previewServe the production build locally
npm run lintESLint over the whole tree

10 The skill filter

Three React contexts cooperate to make filtering feel instantaneous:

  • SkillKeyContext: owns the slug → pretty-name map and a getSkillTitle() helper.
  • SkillFilterContext: owns the active filter set + toggleSkillFilter() and isActive(skills) predicates.
  • useSkillStats: given a slug, returns { years, projects, firstUsed, lastUsed } by walking the parsed YAML.

Every renderable block calls isActive(theseSkills) and dims itself if no filter matches. The <Filterable> wrapper is the standard primitive; see src/components/Filterable.tsx.

// inside a section/responsibility
const { isActive } = useSkillFilter();
return (
  <div className={isActive(skills) ? '' : 'dimmed'}>
    …
  </div>
);

11 License

Released under the GNU General Public License v3.0 or later. You can use, modify, and redistribute it; derivative works must remain under GPL-3.0+. Full text is in LICENSE.

© 2026 Sara McCutcheon <code@saram.cc>. This program is distributed without warranty; see gnu.org/licenses for details.