Website of the Open Source Design community, built with Hugo and Tailwind CSS, hosted on GitHub Pages.
- Read our Contributor Code of Conduct. By participating in this project online or at events you agree to abide by its terms.
- Before submitting a pull request, it is best to open an issue first to describe the suggestion and to get feedback (except it is a fix for obvious bugs or spelling mistakes).
- Please do not submit AI-generated code changes, issues, documentation, or code reviews.
- You can use & modify everything as long as you credit Open Source Design and use the same license for your resulting work.
- Code: AGPLv3
- Content: Creative Commons Attribution-ShareAlike 4.0
There are several ways to edit content on opensourcedesign.net, all of which require a GitHub account:
-
Using GitHub's file editor - Quick edits directly in your browser. Navigate to any file, click the pencil icon, and submit a pull request.
-
Using GitHub Codespaces - A cloud development environment in your browser with Hugo preconfigured and a live preview. From the repo menu choose Code β Codespaces β Create codespace, or click:
-
Setting up locally - For more extensive development work. See the instructions below.
Before you begin, ensure you have the following installed:
- Hugo (extended version; matches CI)
- Git - Download Git
That's all β no Node.js or npm required for everyday work. See How the CSS Works for how styling is built locally vs on the live site. Two optional standalone binaries:
- Pagefind (optional) - generates the search index; only needed to test search locally (CI downloads it automatically)
- Tailwind standalone CLI (optional) - only needed to regenerate
compiled.cssor the vendored typography stylesheet locally
macOS (using Homebrew):
brew install hugoLinux (Debian/Ubuntu):
# Download the extended .deb (version pinned to match CI)
wget https://github.com/gohugoio/hugo/releases/download/v0.164.0/hugo_extended_0.164.0_linux-amd64.deb
sudo dpkg -i hugo_extended_0.164.0_linux-amd64.deb
# Verify installation
hugo versionLinux (Arch):
sudo pacman -S hugoLinux (Fedora):
sudo dnf install hugoWindows (using Chocolatey):
choco install hugo-extendedWindows (using Scoop):
scoop install hugo-extendedFor other platforms, download from the Hugo releases page.
-
Fork and clone the repository
git clone https://github.com/YOUR-USERNAME/opensourcedesign.net.git cd opensourcedesign.net -
Start the development server
hugo server
That's it β styling works out of the box. See How the CSS Works for what happens behind the scenes.
-
Open your browser at
http://localhost:1313/
No build step: edit assets/css/main.css (custom theme, components, base styles) or add any standard Tailwind utility class to templates in layouts/ β both take effect on the next reload with plain hugo server.
The only exception is prose-* typography classes: since the Play CDN cannot load Tailwind plugins, the @tailwindcss/typography styles are pre-compiled into the checked-in assets/css/typography.css. If you need a prose-* class that isn't in there yet, add it to assets/css/typography.src.css and regenerate with the Tailwind standalone CLI (a single executable β it bundles the typography plugin, so no Node.js or npm is required):
tailwindcss -i assets/css/typography.src.css -o assets/css/typography.css --minify
# rebuilds assets/css/typography.css - commit itThe site's look-and-feel comes from Tailwind utility classes in the HTML templates (layouts/) plus custom rules in assets/css/main.css. Together they are compiled into assets/css/compiled.css. That file is not checked into Git (it is in .gitignore) and must be generated before a production build.
On the deployed website, GitHub Actions generate it on every deploy.
For local testing, you have two options:
- Just run Hugo (
hugo server) β no CSS build needed. Ifcompiled.cssis missing,layouts/_default/baseof.htmlfalls back to the Tailwind Play CDN (loaded from jsDelivr): your browser builds the styles on the fly from the page markup and inlinedmain.css. - Match production β compile the CSS yourself, then run Hugo:
(Or use the Tailwind standalone CLI β no Node.js needed.)
npx @tailwindcss/cli -i assets/css/main.css -o assets/css/compiled.css --minify hugo server
assets/css/dark-theme.css is always loaded as a separate file (dark-mode overrides). Article typography (prose classes) uses the checked-in assets/css/typography.css β see Working on styles above to regenerate it.
opensourcedesign.net/
βββ .devcontainer/ # GitHub Codespaces config (Hugo + preview on port 1313)
βββ .github/
β βββ path-filters.yml # Shared CI path allowlists (site / content / forms)
β βββ scripts/ # announce-jobs.mjs - posts new jobs to Mastodon & Bluesky
β βββ workflows/ # hugo-build (deploy), job-approved-email / job-rejected-email,
β # job-expire (daily auto-expiry), job-announce (social media),
β # content-lint (PR checks), preview (PR previews), link-check (weekly)
βββ archetypes/ # Hugo content templates for new pages
βββ assets/
β βββ css/
β βββ main.css # Custom styles + Tailwind theme (source for compiled.css)
β βββ dark-theme.css # Dark-mode overrides (always loaded)
β βββ compiled.css # Generated at build time (gitignored β see βHow the CSS Worksβ)
β βββ typography.src.css # Source/safelist for the typography (prose) bundle
β βββ typography.css # Pre-compiled prose styles (regenerate via Tailwind CLI, don't edit)
βββ content/ # All website content in Markdown
β βββ about-us/ # About, manifesto, governance, by-laws, code of conduct, how to join
β βββ events/ # Event announcements and write-ups
β βββ jobs/ # Job listings
β βββ resources/ # Curated directory + sub-pages: articles/ and reading.md (bibliography)
β βββ ... # Standalone pages (forum, imprint, brand, etc.)
βββ data/ # YAML data files for dynamic content
βββ layouts/ # Hugo HTML templates
βββ static/ # Static assets (images, fonts, downloads)
β βββ images/
β βββ brand/ # Official logos and branding assets
βββ hugo.toml # Hugo configuration
βββ workers/ # Cloudflare Worker for the job, event & resource submission forms (deployed separately)
| Command | Description |
|---|---|
hugo server |
Start the development server with live reload |
hugo --minify --gc |
Build the site for production into public/ |
pagefind --site public |
Generate the Pagefind search index (standalone binary, runs after the Hugo build) |
npx @tailwindcss/cli -i assets/css/main.css -o assets/css/compiled.css --minify |
Build production CSS (optional locally; CI does this automatically) |
tailwindcss -i assets/css/typography.src.css -o assets/css/typography.css --minify |
Regenerate the vendored prose stylesheet (standalone binary) |
GitHub Actions workflows skip expensive Hugo builds when a change cannot affect the live site. Path allowlists live in .github/path-filters.yml:
| Filter | Paths | Used by |
|---|---|---|
site |
content/, data/, layouts/, assets/, static/, hugo.toml, β¦ |
Production deploy, PR previews, accessibility checks |
content |
Job/event/resource markdown and curated YAML | Content lint |
forms |
Submission form layouts and JS modules | Documented for form-related checks |
Production (hugo-build.yml) runs on pushes to main only when site paths change. Use Actions β Build and Deploy Hugo β Run workflow to force a full deploy (e.g. after a docs-only merge when you want to refresh caches).
Pull requests: the preview and accessibility workflows always start, but the Hugo build step is skipped when the PR touches only docs, README, or workers/. A bot comment notes when the preview was skipped. Preview cleanup on PR close always runs so gh-pages/pr-preview/ does not accumulate stale folders.
Cheap checks (ci-checks.yml, content-lint.yml) still run when their respective paths change; content lint only examines files changed in the commit or PR, not the entire corpus.
Search: Search is a site-wide modal (opened from the header search button, or with Ctrl/β + K or /), powered by Pagefind. The index lives in
public/pagefind/and is generated by running thepagefindbinary against the built site; the Pagefind assets are lazy-loaded the first time the dialog is opened. Duringhugo serverthe index is not built, so the modal only returns results after a production build followed bypagefind --site public.
Images: Markdown images are processed through Hugo's asset pipeline (
layouts/partials/resolve-image.html). Local raster images placed underassets/images/are automatically down-scaled and served as AVIF + WebP with the original as a fallback, with intrinsicwidth/heightto avoid layout shift. SVGs, GIFs, and external URLs are passed through untouched.
- Hugo - Fast static site generator written in Go
- Tailwind CSS v4 - Utility-first CSS framework; compiled in the browser via the Play CDN locally, or ahead of time by GitHub Actions in production (see How the CSS Works)
- @tailwindcss/typography - Beautiful typographic defaults for Markdown content (pre-compiled into
assets/css/typography.css) - Inter - Self-hosted variable font (woff2, in
static/fonts/) - Pagefind - Static, client-side search index generated at build time
All content lives as Markdown in content/. The site uses the following sections after the information-architecture overhaul:
| Section | Path | What it holds |
|---|---|---|
| About Us | content/about-us/ |
About page, manifesto, governance, by-laws, code of conduct, how to join |
| Events | content/events/ |
Event announcements and write-ups β or use the event form |
| Jobs | content/jobs/ |
Job listings β or use the job form |
| Resources | content/resources/ |
Hub page with sub-pages: the curated links directory at /resources/links/ (suggest new links via the suggest form), the bibliography at /resources/bibliography/ (/resources/reading/ redirects there), and community articles at /resources/articles/ (/articles/ redirects there) |
| Standalone | content/*.md |
forum, imprint, brand, and the homepage (_index.md) |
The recommended workflow for any content change:
- Edit the Markdown in the relevant
content/folder (see the table above). For quick fixes use GitHub's pencil icon; for larger work, set up the project locally and runhugo serverto preview. - Preview locally at
http://localhost:1313/to confirm formatting, front matter, and links render correctly. - Open a pull request. Content PRs are reviewed by two Open Source Design community members before they are merged and published.
- Cross-team content edits (copy rewrites, page merges) are coordinated by the community - major restructuring is tracked in the project's GitHub issues before landing on a branch.
Note: the
articles/andpeople/sections were removed in the IA overhaul. Article-style content now lives under Resources, and contributor profiles are handled on the forum and the How to Join page.
- Navigate to
content/jobs/ - Create a new
.mdfile following the existing format (date-prefixed filename, e.g.2025-03-15-role-title.md) - Or use the online form at opensourcedesign.net/jobs/job-form/
Links in how_to_apply automatically get a recognisable service icon on the job page (GitHub, GitLab, Codeberg, F-Droid, Figma, Matrix, LinkedIn, app stores, β¦) based on the URL's host; unknown hosts fall back to a generic link icon and emails get an envelope. To support another service, add a host rule and its SVG path to layouts/partials/apply-icon.html.
The form has a Preview button that renders the posting client-side (badges, description Markdown, deliverables, apply links) exactly as it will appear, updating live while you type. Paid postings can also carry a structured rate (rate_min, optional rate_max, rate_currency, rate_period front matter) which renders as e.g. "β¬50β75 per hour" in the job page's Details card; the free-text paid_details remains for notes like "negotiable".
On submission the Worker runs a duplicate check against the live job index (/jobs/index.json, built by Hugo from layouts/jobs/section.json.json): if an open posting with the same or a very similar title exists, the form shows it and asks for confirmation before creating the PR.
To update an existing posting (fix details, mark it as filled or closed), use the "Edit this posting" link in the sidebar of the job's page - it opens the same form prefilled, and submits a moderated pull request that updates the file in place.
Around six weeks after publication, posters of still-open jobs get a reminder email asking whether the position is still open (job-reminder.yml, weekly; it maps each file back to its submission PR and pulls the email from the Worker's KV store - manually committed postings are skipped).
Expiration: postings can carry an optional deadline: YYYY-MM-DD (application deadline, settable from the form). Once the deadline passes, the posting shows an "expired" notice and moves from /jobs/ to /jobs/archive/. A daily GitHub Action (job-expire.yml) also flips status: searching to status: expired when the deadline has passed, or when a posting is over a year old with no update - so the front matter catches up with what the site already shows. Re-open an expired posting by editing it and selecting "Still searching" (clear or move the deadline first).
Feeds: besides the main /jobs/feed.xml, filtered feeds exist at /jobs/feed-paid.xml and /jobs/feed-volunteer.xml (linked from the jobs page) so people can subscribe only to the postings they care about.
Social cards: every job page gets a generated 1200Γ630 Open Graph image (horizontal brand logo, title, organization, paid/volunteer, deadline on a light slate-50 background) so shared links unfurl nicely. They're built by layouts/partials/social-card.html from the committed assets/images/og-card-base.png plus the Inter fonts in assets/fonts/og/; Hugo composites the job-specific text at build time. A posting with an explicit image front matter keeps that image instead.
Content lint: pull requests touching content/jobs/ or content/events/ run content-lint.yml, which validates front matter (required fields, status enums, ISO dates) and catches the classic Markdown pitfalls (4-space indents rendering as code blocks, ###Heading without a space, β’ pseudo-bullets) before a broken page can be merged.
When a push to main adds a new file under content/jobs/ (i.e. a submission PR was merged, or a maintainer committed a posting directly), the job-announce.yml workflow posts it to Mastodon and Bluesky using .github/scripts/announce-jobs.mjs - no third-party services involved. It waits for the job page to be live (the Hugo deploy runs in parallel), announces only fresh status: searching postings (dated within 14 days, so bulk imports and renames never spam the feeds), and caps posts per run. Posters can opt out on the job form (announce_social: false in front matter); older postings without that field are announced as before.
Each platform is optional - configure its repo secrets to enable it:
| Platform | Secrets | Where to get them |
|---|---|---|
| Mastodon | MASTODON_URL, MASTODON_ACCESS_TOKEN |
On the account's instance: Preferences β Development β New application, scope write:statuses. MASTODON_URL is the instance base URL, e.g. https://mastodon.online. |
| Bluesky | BLUESKY_IDENTIFIER, BLUESKY_APP_PASSWORD (optional BLUESKY_SERVICE) |
BLUESKY_IDENTIFIER is the handle (e.g. opensourcedesign.net); create the app password under Settings β Privacy and Security β App Passwords. Don't use the account password. |
To announce a posting manually (retry, or one that predates the workflow), run Announce new jobs on social media from the Actions tab with the file path - there's also a dry-run option that composes the posts without publishing. Missed announcements never block a merge: the workflow only reads the repo and fails loudly in the Actions log.
- Navigate to
content/events/ - Create a new
.mdfile (date-prefixed for announcements, or a short slug for write-ups, e.g.fosdem-2026.md) - Fill in the front matter with event details (
title,eventDate,status, location, etc.) - Or use the online form at opensourcedesign.net/events/event-form/, which opens a moderated pull request for you (handled by the same Cloudflare Worker as the job and resource forms)
To update an existing event (fix details, mark it as cancelled), use the "Edit this event" link at the bottom of the event's page - it opens the same form prefilled, and submits a moderated pull request that updates the file in place.
Announcements vs. write-ups: add an
authorto the front matter to mark a page as a write-up / recap. Those entries get a "Recap" card (with a thumbnail pulled from the first image in the body) in the Write-ups & Recaps column on/events/and surface on the homepage. Pages without anauthorare treated as plain listings. Setstatustoupcoming/startedto appear in the Upcoming list, orpast/cancelledto move into the archive.Add an
endDate(a machine-readable date, e.g.endDate: 2026-02-01) to dated events. Once that date passes, the event is automatically dropped from Upcoming and moved into Past Events even if itsstatusstill saysupcoming- so a forgotten status never leaves a finished event sitting at the top of the page.
- To add, change, or remove a curated tool or link, edit
data/resources.yaml- each entry is a few YAML lines (name,url, optionaldescriptionand extralinks), grouped into categories. No HTML or template knowledge needed; the file's header comment documents the format. Non-Git users can use the suggest form instead (linked from/resources/links/) - it opens a moderated pull request that inserts the entry into the right category, via the same Cloudflare Worker as the job and event forms. - To add a talk, article, paper, or book to the Bibliography, add an entry to
data/bibliography.yaml. - Both lists render wherever their shortcode is placed in a page's Markdown:
{{</* resources */>}}for the filterable directory (incontent/resources/links.md) and{{</* bibliography */>}}for the bibliography (incontent/resources/bibliography.md, withheading="false"since the page provides its own title). Move or copy a shortcode to relocate its list. - New resource sub-pages (e.g. the guide texts proposed in issue #554) are Markdown files in
content/resources/withlayout: resource-pageand aweightthat controls their order on the/resources/hub. Two of the four proposed guides are still draft placeholders - fill in the body and removedraft: trueto publish.
- Navigate to
content/about-us/ - Edit the relevant Markdown file (
_index.md,manifesto.md,governance.md,by-laws.md,code-of-conduct.md)
Additional contributor guides live in the docs/ folder - including the moderation guide for maintainers reviewing job, event, and resource submissions.
Content that appears on multiple pages is managed through YAML files in data/:
| File | Description |
|---|---|
social.yaml |
Social media links and icons |
supporters.yaml |
Supporter/sponsor logos |
conferences.yaml |
Conference partnerships |
affiliates.yaml |
Affiliate organizations |
tools.yaml |
Featured open source design tools (homepage) |
resources.yaml |
Curated resources directory shown on /resources/links/ |
bibliography.yaml |
Bibliography shown on /resources/bibliography/ |
quicklinks.yaml |
Footer navigation links |
summits.yaml |
Past summit event information |
The site uses Tailwind CSS v4 with CSS-based configuration. See How the CSS Works for the full compile/fallback pipeline. To modify styles:
- Edit
assets/css/main.cssfor custom components and base styles (it supports the full Tailwind syntax, including@applyand@theme, and is the single source for both pipelines) - Edit
assets/css/dark-theme.cssfor dark-mode overrides - Use Tailwind utility classes directly in HTML templates (
layouts/) - Run
hugo serverto see changes with live reload - Only
prose-*typography classes are pre-compiled - see Working on styles above if you need a new one
Custom component classes (prefixed with osd-) are defined in main.css:
.osd-card- Card component styling.osd-pill- Tag/badge styling.osd-btn-primary- Primary call-to-action button.osd-prose- Article content styling.osd-lightbox-*- Image lightbox components
This project exists thanks to all the people who contribute.
Thank you to all our backers! π (Become a backer)
Support this project by becoming a sponsor. (Become a sponsor)
Please note that Open Source Design has a Contributor Code of Conduct. By participating in this project online or at events you agree to abide by its terms.
π You can use & modify everything as long as you credit Open Source Design and use the same license for your resulting work.
- Code: AGPLv3
- Content: Creative Commons Attribution-ShareAlike 4.0