- README: add a11y badge linking to docs/accessibility.md. - CHANGELOG: full v0.3.0 entry under Unreleased (Added/Changed/Fixed/ Deferred-to-v0.3.1). Documents per-kind CSS bundles, Tier A features, Lighthouse-relevant polish, contrast-token visual diff, tap-target sizing decisions, and Hugo CI matrix. - docs/accessibility.md: new — WCAG 2.2 AA conformance summary, Lighthouse measurement instructions, projected baseline (TBD entries to fill after first production deploy), known limitations. - docs/customization.md: cover-image override snippet (v0.4.0 native pipeline promise); document breadcrumbs/prev-next/lang-switcher opt-ins. - plans/260510-0144-tsuki-v0.3.0/: mark all phases completed; note narrow-TOC <details> deferral to v0.3.1; record audit-pass scope additions integrated into Phases 2–6. - plans/reports/: add researcher-260515-tsuki-vs-stack-papermod-feature -gap.md (verdict: feature parity on must-haves; Tier A gaps shipped this round) + code-reviewer-260515-lighthouse-80-baseline-audit.md (projected ≥80 on all 4 categories; ≥95 a11y achievable with shipped fixes).
6.6 KiB
Customization
Hugo's lookup order means anything in your site overrides the theme. No fork required.
Override layouts and partials
Drop a file with the same path under your site's layouts/ to replace the theme version.
your-site/
└── layouts/
├── _partials/
│ └── footer.html # overrides themes/tsuki/layouts/_partials/footer.html
└── single.html # overrides themes/tsuki/layouts/single.html
To extend rather than replace, copy the theme partial into your site, then modify. Common overrides:
_partials/footer.html— add license, build info, web ring links_partials/home/hero.html— restructure the homepage hero_partials/comments.html— swap Giscus for a different provider
Override design tokens
Tokens are CSS custom properties on :root and :where([data-theme="dark"]). Override them in a custom stylesheet without editing the theme.
/* your-site/assets/css/custom.css */
:root {
--tsuki-accent: #cc7a3b; /* warmer accent */
--tsuki-font-sans: "Public Sans", system-ui, sans-serif;
--tsuki-content-width: 48rem; /* wider posts */
}
:where([data-theme="dark"]) {
--tsuki-accent: #f0a070;
}
Append it to the bundle by overriding _partials/head.html. Copy the theme version, add your file:
{{- $cssFiles := slice
(resources.Get "css/tokens.css")
(resources.Get "css/reset.css")
...
(resources.Get "css/view-transitions.css")
(resources.Get "css/custom.css") {{/* your override, last in cascade */}}
-}}
Add custom icons
Drop SVGs into assets/icons/<name>.svg, then reference by name:
# data/profile.yaml
links:
- icon: bluesky
label: Bluesky
url: https://bsky.app/profile/...
Use currentColor for fill/stroke so icons inherit text color in light and dark modes:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
<path d="..."/>
</svg>
Self-host fonts
The theme stack is "Inter", "Be Vietnam Pro", system-ui, .... The first two fall back to system fonts when missing. To self-host:
- Subset Be Vietnam Pro + Inter to
vietnamese,latinranges. - Drop the woff2 files into
static/fonts/. - Add
@font-facedeclarations inassets/css/custom.css(see "Override design tokens" above).
@font-face {
font-family: "Inter";
src: url("/fonts/Inter-VariableFont_slnt,wght.woff2") format("woff2-variations");
font-weight: 100 900;
font-display: swap;
}
@font-face {
font-family: "Be Vietnam Pro";
src: url("/fonts/BeVietnamPro-Regular.woff2") format("woff2");
font-weight: 400;
font-display: swap;
}
Override colors per-page
Set CSS variables inline via a frontmatter style you wire up yourself, or use the body_class block from baseof.html:
<!-- layouts/section/work.html -->
{{ define "body_class" }}list section-work{{ end }}
Then style body.section-work { --tsuki-accent: #...; } in your custom CSS.
Callouts (admonitions)
Use Hugo's native Markdown alert syntax (Hugo 0.140+) — no shortcode needed:
> [!note]
> Useful side information.
> [!tip]
> Reader-friendly hint.
> [!important]
> Hard requirement.
> [!warning]
> Heads-up about a pitfall.
> [!caution]
> Risk of breakage.
Titles localize via i18n/vi.yml keys calloutNote, calloutTip, calloutImportant, calloutWarning, calloutCaution. Override per-callout with > [!note] Custom title.
Plain > blockquote still renders as the regular muted blockquote — only [!type] triggers callout styling.
Override callout colors in your custom CSS — each callout type exposes the --tsuki-callout and --tsuki-callout-bg tokens through its modifier class:
/* your-site/assets/css/custom.css */
.callout-note { --tsuki-callout: #ff5d8f; --tsuki-callout-bg: #ff5d8f1f; }
Show word count
Reading time is shown on every post by default. Add word count under it with:
params:
showWordCount: true # default false
The byline reads 5 phút đọc · 1024 từ (translated via the wordCount i18n key).
Accessibility & SEO defaults
The theme includes accessibility features that require no configuration:
- Skip-link — first focusable element on every page; jumps to
<main id="main">(WCAG 2.1 M3) - Focus rings — visible outline via
--tsuki-accenton:focus-visiblefor keyboard navigation - Lastmod byline — shows "Cập nhật {date}" when modified date is ≥24h newer than publish date
- Lazy-load images — all in-content images get
loading="lazy" decoding="async"via render hook - External link security — all Markdown links to
http:///https://///getrel="noopener noreferrer"automatically
All of these are transparent — no override hooks needed unless you want to customize the markup.
Disable features
Every interactive feature has a kill switch:
params:
search:
enable: false # removes search button + /search/ route
comments:
giscus:
enable: false # comments partial becomes a no-op
Per-post:
toc: false
comments: false
Cover images
cover.image is currently used for OG / Twitter card metadata only — the theme does not render a hero cover above the post title by default. This avoids LCP regressions on themes with un-optimised consumer-supplied images.
To render a cover, override layouts/single.html in your site:
{{ define "main" }}
{{ partial "breadcrumbs.html" . }}
<article class="post">
<header class="post-header">
{{- with .Params.cover.image }}
{{- /* Use Hugo image processing for a responsive, hashed asset */ -}}
{{- $img := resources.Get . | images.Resize "1200x" -}}
<img class="post-cover"
src="{{ $img.RelPermalink }}"
width="{{ $img.Width }}" height="{{ $img.Height }}"
alt="" fetchpriority="high" decoding="async">
{{- end }}
<h1 class="post-title">{{ .Title }}</h1>
{{ partial "meta.html" . }}
</header>
<div class="post-content" data-pagefind-body>{{ .Content }}</div>
</article>
{{ partial "prev-next.html" . }}
{{ partial "related-posts.html" . }}
{{ partial "comments.html" . }}
{{ end }}
A built-in cover pipeline with srcset/AVIF is planned for v0.4.0.
Breadcrumbs, prev/next, language switcher
These ship in v0.3.0 as opt-in features.
params:
breadcrumbs:
enable: true # Home › Section › Page trail + BreadcrumbList JSON-LD
prevNextNav:
enable: true # in-section prev/next post nav below content
The language switcher partial auto-emits when hugo.IsMultilingual is true; no config flag needed.