Byline

Documentation · Version 1.0.0

Byline

Thank you for choosing Byline. This guide covers everything from opening your first page to connecting a form. If you only read one section, read Quick start.

Byline is an editorial landing page template for creators who sell courses, paid newsletters, memberships and digital products. It ships as plain HTML built with Tailwind CSS 4, with 4 home layouts, 21 pages and 43 interchangeable sections, in light and dark themes and in both text directions.

What is in the download

byline/
├── template/
│   ├── ltr/                 21 HTML pages, left to right
│   │   ├── index.html … sections.html
│   │   └── assets/
│   │       ├── css/app.css  one compiled stylesheet
│   │       ├── js/app.js    one JavaScript bundle
│   │       ├── fonts/       self-hosted WOFF2
│   │       └── img/         placeholders and interface plates
│   └── rtl/                 the same 21 pages, right to left
├── source/                  the Eleventy authoring layer
├── LICENSES.md
└── CHANGELOG.md

Most people only need the template folder. Every file in it is finished HTML: open it, edit the text, upload it. The source folder is the authoring layer the pages are built from, for when you want to change the navigation or footer once and have every page follow.

Quick start

  1. Unzip the download and open template/ltr/index.html in your browser. No server is needed.
  2. Choose the home layout that fits what you sell and rename it to index.html. Delete the homes you do not need.
  3. Open the page in a code editor and replace the demo text with your own.
  4. Replace the placeholder images. See Images.
  5. Connect your forms to a form service. See Forms.
  6. Upload the contents of the ltr folder, or the rtl folder, to any static host.

Keep the assets folder next to the HTML files. Every path in the template is relative, so the site works from any folder or subdirectory.

Pages

Every page exists in both ltr and rtl, with the same file name.

FilePagePreview
index.htmlHome: Course and Cohort LaunchView
index-newsletter.htmlHome: Paid NewsletterView
index-community.htmlHome: Membership CommunityView
index-shop.htmlHome: Digital Product ShopView
about.htmlAboutView
pricing.htmlPricingView
product.htmlProduct DetailView
checkout.htmlCheckoutView
blog.htmlBlogView
blog-post.htmlBlog PostView
faq.htmlFAQView
contact.htmlContactView
login.htmlLog InView
signup.htmlSign UpView
forgot-password.htmlForgot PasswordView
thank-you.htmlThank YouView
coming-soon.htmlComing SoonView
404.html404View
terms.htmlTermsView
privacy.htmlPrivacyView
sections.htmlSections LibraryView

Working with the source

The pages are generated with Eleventy from Nunjucks partials and plain JavaScript data files. You need Node.js 20 or newer.

cd source
npm install
npm run dev      # build once, then watch on http://localhost:8080

The build writes both direction folders to dist/template. The scripts you will use most:

npm run build         # the whole package, both directions
npm run dev           # watch mode
npm run placeholders  # redraw a placeholder for every image slot
npm run check         # contrast and performance budget checks
node tools/audit.mjs  # structural and accessibility audit

Where things live

  • src/pages/ has one file per page.
  • src/_includes/sections/ has one partial per block, grouped by type.
  • src/_data/ holds all of the copy. Change text here, not in the partials.
  • src/_data/site.js holds the site name, contact details, navigation and footer links.
  • src/css/app.css is the stylesheet and the Tailwind configuration in one file.
  • src/js/ has one module per behavior.

Colors

Byline uses one accent color and a set of paper and ink tones. They are defined once as design tokens at the top of src/css/app.css, and every utility class reads from them.

@theme static {
  --color-paper:      #f7f4ee;  /* page ground */
  --color-ink:        #14120f;  /* headings and body */
  --color-accent:     #e63a1f;  /* fills, underlines, marks */
  --color-accent-ink: #b32712;  /* accent text and button fills */
  /* … */
}

The dark palette overrides the same tokens a little further down the file:

@layer theme {
  :root[data-theme="dark"] {
    --color-paper:      #0f0e0c;
    --color-ink:        #f2ede4;
    --color-accent:     #ff5c3d;
    --color-accent-ink: #ff7b60;
    /* … */
  }
}

To rebrand, change --color-accent and --color-accent-ink in both blocks. Button labels sit on --color-accent-ink, so keep that shade dark enough in the light theme for a contrast ratio of at least 4.5:1 against the paper color. npm run check:contrast tests every documented pair for you.

Using only the compiled template? The same tokens are at the top of assets/css/app.css as CSS custom properties, so you can edit the values there directly.

Typography

RoleFamilyUsed for
DisplayInstrument SerifHeadlines and large numerals
TextGeistBody copy and interface
MetaGeist MonoEyebrows, labels and dates
ArabicIBM Plex Sans ArabicEverything in the RTL build

All four are self-hosted WOFF2 files in assets/fonts, so the template makes no request to Google Fonts. To use a different typeface, add its @font-face rules to the stylesheet and change the family tokens:

--font-display: "Instrument Serif", "Iowan Old Style", Georgia, serif;
--font-sans:    "Geist", ui-sans-serif, system-ui, sans-serif;
--font-mono:    "Geist Mono", ui-monospace, Menlo, monospace;

Dark mode

The theme toggle in the navigation switches between light and dark and remembers the choice inlocalStorage under the key byline-theme. A small inline script in the page head applies the saved theme before the page paints, so there is never a flash of the wrong theme.

Light is the default, and the operating system setting is deliberately not used. To make dark the default, add the attribute to the <html> element:

<html lang="en" dir="ltr" data-theme="dark">

To remove dark mode entirely, delete the theme toggle from the navigation. Without it, the saved preference is never set and every page stays light.

Right to left

The rtl folder is a complete build of every page with dir="rtl",lang="ar", Arabic demo copy and IBM Plex Sans Arabic. The layout mirrors automatically because the stylesheet uses logical properties throughout, so the same CSS file serves both folders.

  • For an Arabic, Hebrew, Persian or Urdu site, start from the rtl folder and replace the demo copy.
  • The Arabic text is placeholder copy, not a translation. In the source, it is produced by thearabic transform in eleventy.config.mjs. Delete that transform to get an English right-to-left build, then add your own copy.
  • Strings written by JavaScript, like validation messages and theme labels, live in src/_data/ui.js.

Images

The photographs in the live demo are for preview only and are not included in the download. Every image in the download is a placeholder that shows its file name, shape and the size to use for your replacement.

Each image slot is a set of WebP files in assets/img/photos, one per width, named<slot>-<width>.webp. The page picks the right width for the screen:

<img src="assets/img/photos/portrait-instructor-480.webp"
     srcset="assets/img/photos/portrait-instructor-160.webp 160w, …"
     width="960" height="1200" alt="…">

Replacing a placeholder

  1. Crop your photo to the slot's aspect ratio.
  2. Export it as WebP at every width listed for that slot.
  3. Overwrite the placeholder files, keeping the same names. No markup changes are needed.
  4. Update the alt text to describe your photo.

When you use photos of people, make sure you have their permission, especially next to a testimonial, a name or a job title.

SlotFull sizeRatioWidths
avatar-01320 × 3201:196, 192, 320
avatar-02320 × 3201:196, 192, 320
avatar-03320 × 3201:196, 192, 320
avatar-04320 × 3201:196, 192, 320
avatar-05320 × 3201:196, 192, 320
avatar-06320 × 3201:196, 192, 320
avatar-07320 × 3201:196, 192, 320
avatar-08320 × 3201:196, 192, 320
portrait-martin960 × 12004:5160, 320, 480, 720, 960
portrait-obi960 × 12004:5160, 320, 480, 720, 960
portrait-daniel960 × 12004:5160, 320, 480, 720, 960
portrait-seamus960 × 12004:5160, 320, 480, 720, 960
portrait-instructor960 × 12004:5160, 320, 480, 720, 960
cover-011200 × 8003:2480, 800, 1200
cover-021200 × 8003:2480, 800, 1200
cover-031200 × 8003:2480, 800, 1200
cover-041200 × 8003:2480, 800, 1200
cover-051200 × 8003:2480, 800, 1200
cover-061200 × 8003:2480, 800, 1200
cover-071200 × 8003:2480, 800, 1200
cover-081200 × 8003:2480, 800, 1200
cover-091200 × 8003:2480, 800, 1200
scene-studio960 × 12004:5160, 320, 480, 720, 960
scene-meetup1200 × 8003:2480, 800, 1200
scene-meetup-tall960 × 12004:5160, 320, 480, 720, 960
hero-fullbleed2000 × 85721:9640, 960, 1440, 2000
hero-newsletter960 × 12004:5160, 320, 480, 720, 960
product-01960 × 12004:5160, 320, 480, 720, 960
product-hero1200 × 9004:3480, 800, 1200
product-spotlight1200 × 9004:3480, 800, 1200
product-02960 × 12004:5160, 320, 480, 720, 960
product-03960 × 12004:5160, 320, 480, 720, 960
product-04960 × 12004:5160, 320, 480, 720, 960
product-05960 × 12004:5160, 320, 480, 720, 960
product-06960 × 12004:5160, 320, 480, 720, 960
product-07960 × 12004:5160, 320, 480, 720, 960
product-08960 × 12004:5160, 320, 480, 720, 960

Sections

The Sections Library page shows all 43 blocks. Each block's outer element carries a data-section attribute, so you can find it in any page and copy it into another.

In the HTML template

Search the page for data-section="pricing-two-tier", for example, and copy that element from its opening tag to its closing tag. Paste it where you want it in the other page. Blocks share one stylesheet and one script, so there is nothing else to copy.

In the source

Each block is a partial that reads one variable, s. Set it, then include the partial:

{% set s = course.curriculum %}
{% include "sections/content/curriculum.njk" %}
GroupBlockdata-section
NavigationTransparent over heronavigation-transparent
Solid borderednavigation-solid
Centered logonavigation-centered
AnnouncementStaticannouncement-static
Countdownannouncement-countdown
HeroPress sliderhero-press
Course splithero-course
Newsletter mastheadhero-newsletter
Community avatarshero-community
Shop splithero-shop
Centered editorialhero-centered
Full-bleed imagehero-fullbleed
Logo stripStatic hairline gridlogos-grid
Marqueelogos-marquee
Feature / benefitNumbered listfeature-numbered
Alternating 7/5feature-alternating
Hairline 3-upfeature-hairline
Tabbed panelfeature-tabs
StatsCounter band on accentstats-band
Inline hairline rowstats-inline
ContentCurriculum accordioncontent-curriculum
Events listcontent-events
Timelinecontent-timeline
Media / productFilterable product gridmedia-grid
Featured spotlightmedia-spotlight
Gallery mosaicmedia-gallery
TestimonialsPull-quote carouseltestimonials-carousel
Two-up on accenttestimonials-duo
Portrait cardstestimonials-portraits
PricingTwo tiers with payment planpricing-two-tier
Three tiers, monthly / annualpricing-three-tier
Free versus paid comparisonpricing-comparison
FAQAccordionfaq-accordion
Two columnfaq-two-column
Call to actionFull-bleed accent bandcta-band
Inline newslettercta-newsletter
Split with mediacta-split
FormsInline email captureforms-inline
Contactforms-contact
Applicationforms-application
FooterFour column with newsletterfooter-columns
Minimal editorialfooter-minimal
Mega sitemapfooter-sitemap

Forms

Byline is an HTML template and does not include a backend. Every form validates in the browser and shows success and error states, but it needs a form service or your own server to actually send anything.

Add a data-endpoint attribute to the <form> and submissions are posted there with fetch. Without it, the form runs a short demo and shows the success message.

Formspree

<form data-form data-endpoint="https://formspree.io/f/your-id" method="POST">

Basin

<form data-form data-endpoint="https://usebasin.com/f/your-id" method="POST">

Netlify Forms

<form data-form data-netlify="true" name="contact"
      data-endpoint="/" method="POST">
  <input type="hidden" name="form-name" value="contact">

Every field needs a name attribute to be sent. Error messages come fromsrc/_data/ui.js, and a single field can override its message withdata-error-required or data-error-pattern.

Motion

Byline uses smooth scrolling (Lenis), parallax, number counters, a marquee, scroll reveals and a carousel (Embla). All of it stops when the visitor has turned on the reduced motion setting in their operating system. The page then scrolls natively, counters show their final value and nothing slides into place.

Content never depends on motion. Reveals only start hidden once JavaScript has loaded, so a page with scripts blocked shows everything.

You can switch motion off for every visitor with an attribute on the <html> element:

<html lang="en" dir="ltr" data-motion="off">

Adding ?motion=off to a page's address does the same for one visit, which is useful for screenshots.

JavaScript

All behavior is in assets/js/app.js, a single ES module with no jQuery or framework. Each feature is its own module in src/js and attaches to elements through a data-* attribute. A page without that attribute simply skips the module, and one module failing never stops the others.

ModuleWhat it does
theme.jsLight and dark toggle
nav.jsSticky navigation, dropdowns and the mobile drawer
smooth-scroll.js, parallax.jsSmooth scrolling and parallax
reveal.js, counter.jsScroll reveals and number counters
carousel.js, press.jsTestimonial carousel and the press hero slider
accordion.js, tabs.jsAccordions and tabbed panels
countdown.jsCountdown to the cohort start date
filter.jsProduct category filters
price-toggle.jsMonthly and annual, or full and payment plan, prices
forms.jsValidation and submission
copy.jsCopy buttons on the Sections Library page

The countdown date and the seat count live in src/_data/site.js under cohort. In the HTML template, change the date in the countdown element's data-countdown attribute, written as an ISO 8601 date and time.

Accessibility

Byline targets WCAG 2.2 AA and was tested with axe-core on every page. It includes a skip link, correct landmarks and heading order, visible focus styles, focus trapping in the mobile drawer, targets of at least 24 pixels, and form errors connected to their fields with aria-invalid andaria-describedby.

To keep it that way as you edit:

  • Write alt text for every photo you add, or leave it empty for decorative images.
  • Keep one <h1> per page and do not skip heading levels.
  • Check contrast when you change colors.

Browser support

Chrome 111+, Safari 16.4+, Firefox 128+ and Edge 111+. These are the browsers Tailwind CSS 4 supports. Older browsers are not supported.

Credits

AssetAuthorLicense
Instrument SerifRodrigo Fuenzalida, Jordan EgstadSIL Open Font License 1.1
Geist, Geist MonoVercelSIL Open Font License 1.1
IBM Plex Sans ArabicIBM, Bold MondaySIL Open Font License 1.1
Lenisdarkroom.engineeringMIT
Embla CarouselDavid JerlekeMIT

The icons, placeholders and interface illustrations were made for this template. A full list is inLICENSES.md and assets/licenses.csv.

Demo photography

The portraits, scenes and first five product images in the live demo were generated for this project. The member avatars, blog covers and remaining product images are fromUnsplash under the Unsplash License, by Christian Buehner, Dorrell Tibbs, Shoeib Abolhassani, Vitaly Gariev, Kamil Mehmood, Marcos Paulo Prado, Omar Abozeid, Elizeu Dias, Nur demirbaş, Diego Marín, Alex Boyd, Declan Sun, Gustavo Alejandro Espinosa Reyes, Wonderhunt, T (@tanyabarrow), Towfiqu barbhuiya, Duong Nguyen, Renáta-Adrienn, Joanna Kosinska, St James Studio, mk. s and Kelly Sikkema. None of these images are included in the download.

Changelog

1.0.0 2026-09-02

  • First release.
  • 21 pages in both text directions, 42 HTML files in total.
  • 43 sections across 14 groups.
  • Light and dark themes, WCAG 2.2 AA, and full rendering with JavaScript disabled.