011
AI ready Refreshed

Photography Portfolio Template

£29 View live demo or the whole Vault, £79.20

Includes the original v1 design as a separate download.

Five-page portfolio website template for photographers, videographers and illustrators: the whole archive is the home page, every frame small and at its own shape, and a category filter and frame count reflow it in place. Every frame opens its project: twelve commissions and series drawn from one data file, each as a book or a contact sheet. One saturated colour floods whole sections, with five colour-and-type presets. Shown as a Bristol portrait and editorial photographer.

FlipScrollTrigger intermediate
11 more details
flip-layoutfilterview-transitionsclick-togglehover-effectscroll-revealstaggerform-validationkeyboard-navigationprogressive-enhancement 5 colour palettes
Colour

Updated

About this template

A five-page portfolio site built on one idea: the archive is the front door. The home page opens straight into eighty-four photographs at thumbnail scale, each at its true shape in justified rows, with a row of category words and a frame count above it. Choose portrait, editorial, commercial or personal, or switch between thirty-six, sixty and every frame, and the whole field reflows in place while the frames travel to their new positions.

Read the full template overview

Every frame belongs to a project. One page draws any of the twelve from a single data file, led by the frame you opened, which grows out of its thumbnail on the way in. Each project has its own copy, facts and credits, and its frames switch between a book layout and a contact sheet.

Around the archive the design is plain and committed: a warm paper ground, one saturated colour that floods whole sections with big display lines, and a small block that holds the name. Five presets change that colour pair and the display face together. The worked example is Bea Farrant, a portrait and editorial photographer in Bristol; swap the photographs, the projects file and the category words and it suits any photographer, a videographer, an illustrator or a studio.

Photography Portfolio Template - website template preview

What's included

12 items
  • Five complete pages: home archive, full work archive, a data-driven project page, about and contact
  • Archive hero with eighty-four frames at their true aspect ratios in justified rows with one fixed gutter
  • Category filter and a 36 / 60 / all frame count that reflow the field with GSAP Flip, with the section height held and animated so nothing below jumps
  • Twelve worked projects in one editable file (assets/projects.js): copy, facts, credits, optional prints block and every frame with its caption
  • Project pages switch between a twelve-column book layout and a contact sheet; frames without a set position are laid out automatically
  • Cross-document view transition: any archive frame grows into its project's lead image, and shrinks back on Back
  • Projects list and a friendly not-found state with the same list; a readable no-JavaScript project page
  • Work page index view: every frame as a numbered, captioned, linked list with a preview that follows the pointer
  • Five colour-and-type presets, each a saturated flood colour with its own name-block colour and display face, every pair measured
  • Phone menu as a full-height colour sheet in large display type, built on a details element that works without JavaScript
  • Contact page job-type chooser that rewrites the guidance and presets the enquiry form, with inline validation
  • Self-hosted fonts and baked WebP photography, no build step

Perfect for

5 use cases
  • Portrait, editorial, wedding and fashion photographer portfolios
  • Videographers and directors showing stills and project pages
  • Illustrators and artists with a large body of work to browse
  • Photo studios and small agencies presenting several photographers' archives
  • Personal series and exhibition sites with print sales by enquiry

How it works

4 sections

The archive field

Every frame is a list item whose width is the shared row height multiplied by its own aspect ratio, so portraits and landscapes sit together without cropping. Three row heights belong to the three frame counts. The filter words and counts only change which items are shown and which row height applies; GSAP Flip records where every frame was and animates it to where it now is, while frames that leave or arrive fade and scale.

Pages and the lead image morph

The pages are ordinary documents with a short cross-document view transition. The archive frame you open carries the same view-transition name as the project page's lead image, so supporting browsers grow the thumbnail into the lead and shrink it back on Back. Other browsers simply load the page.

Projects from one file

Every commission and series is one entry in a plain data file: its copy, facts, credits, an optional prints block and its frames with captions. The project page reads it and draws the chosen project before the first paint, so the lead image is in place for the morph from the archive. In book mode each frame can name its place on a twelve-column grid, and frames that do not are laid out in alternating pairs and singles.

Fallbacks

Without JavaScript every frame shows at the middle size, the filter and count controls stay hidden, the project page shows one complete project and the list of all twelve, and the menu opens as a native details element. Under reduced motion every control still works as an instant change and the page transitions are turned off. If the GSAP CDN is blocked the controls keep working without animation.

Plugins Flip, ScrollTrigger
Difficulty Intermediate
Colour palettes 5 ready-made palettes, each measured for AA contrast. Switch with one attribute, or copy the values you want into :root. Try them in the preview above.
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this template's demo (6 pages, lowest score shown), from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

Questions about this template

Which kinds of portfolio does this template suit?

Any portfolio where the amount of work is the point: portrait, wedding, fashion and editorial photographers, videographers showing stills, illustrators, artists and small photo studios. The bundled example is a Bristol portrait photographer, but the archive, series page and booking page work the same with your own categories and pictures.

How do I add my own photographs to the archive?

Each frame is one list item in the archive with a category, a caption in the image's alt text and a client line. Copy an item, point it at your image and set its category. The width follows the image's own shape automatically. Keep thumbnails around 240 pixels tall so a full archive stays light.

Can I change the category words and the frame counts?

Yes. The category buttons match the data-cat values on the frames, so rename both together, or add a fifth category with one more button. The three counts are buttons with a data-count value and each has its own row height in the stylesheet, so you can make them 24, 48 and all, for example.

How do I add or change a project?

Projects live in one file, assets/projects.js. Each entry has a slug, title, client, year, category, a few sentences of copy, facts, credits and its frames with captions. Add an entry and its plate images, then link the matching archive frames to project.html with that slug. Frames without a set position on the book grid are laid out automatically.

Does the contact form send enquiries?

No, it is a front end only. It collects name, email, job type, dates, budget and a message, validates them and shows a confirmation, but nothing is sent. Connect it to Formspree, Netlify Forms or your own endpoint before going live; the README shows where the submit handler lives.

Can I use a different colour instead of the yellow?

Yes. Five presets ship: yellow and red with Crimson Pro, coral and navy with Bricolage Grotesque, pink and green with DM Serif Display, sky and vermilion with IBM Plex Mono, and violet and ink with Young Serif. Set data-palette on the html element, or copy one preset's values into the root block. Each pair's measured contrast is listed in the stylesheet.

Paid template

Purchase to unlock the code.

Buying Photography Portfolio Template opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

£29 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £79.20, one payment.

Documentation

Quick Start

  1. Unzip and open index.html in a browser, or serve the folder with any static server (npx serve .).
  2. The pages are plain HTML files: index.html (home archive), work.html, project.html (every project, from assets/projects.js), about.html and contact.html. upriver.html only forwards old links to project.html?p=upriver.
  3. Every page shares assets/style.css and assets/script.js. Home and Work also load assets/archive-layout.js straight after the archive; the project page loads assets/projects.js and assets/project-page.js. There is no build step.
  4. Deploy the folder as it is to any static host.

Using It With Your Own Design

  • Colours: four values at the top of assets/style.css: --paper, --ink, --flood (the yellow) and --slab (the red name block). The comment above them lists the measured contrast of each pair. Four more presets follow (see Palettes). Choose a flood colour that dark type reads on, or set --on-flood to white for a deep colour.
  • Type: Crimson Pro (every statement, the controls and the name) and Public Sans (facts, captions and the form), self-hosted in assets/fonts/. Change the @font-face rules, the two font preloads in each page's head, and --font-display and --font-body.
  • Photographs: the archive works with any mix of shapes. Keep thumbnails about 240 pixels tall; the archive's weight is the number of frames times one thumbnail.
  • Another kind of work: rename the four categories and the archive suits a wedding photographer, a videographer's stills, an illustrator or a small studio with several photographers.

Pages and Configuration

Page Sections Hands-on piece
Home Archive field with caption strip, yellow statement flood, series split, recent commissions, yellow footer Filter words and frame count reflow the archive
Work The full archive, field or index view, yellow footer Filter, count, field/index switch, pointer preview in the index
Project Lead image, yellow title band, essay, every frame of the project, prints (where set) and credits, footer pointing to the next project. With no project named: the list of all projects Book / contact sheet switch; the archive frame you opened becomes the lead
About Portrait and bio, six frames from the series, clients flood, exhibitions, press and how a commission runs The chosen frame opens out
Contact Yellow top flood, job-type chooser, enquiry form, studio and agent Chooser rewrites the guidance and presets the form

Every feature looks for its own markup and returns when it is missing, so you can delete a section, or a page and its links, without editing the script.

The archive (Home and Work)

Each frame is one item in <ul class="archive" data-archive>:

Code snippet omitted: it ships with the download.

  • data-cat must match one of the filter buttons' data-filter values (portrait, editorial, commercial, personal). Rename both together, or add a button for a new category. In the example it is the project's category.
  • --ar is the image's width divided by its height. The frame's width is the row height times this value, so nothing is cropped.
  • The image's alt is the caption the strip shows on hover, focus or tap; data-job is the client line after it.
  • Every frame links to its project: <a class="tile-link" href="project.html?p=westerly&amp;f=r27">. p names the project in assets/projects.js; f makes that frame the lead image.
  • The counts are buttons with data-count="36", "60" and "all". Their row heights are --row-36, --row-60 and --row-all in :root (and again in the phone media query). Change the numbers on the buttons and the script follows.
  • The first frames load eagerly and the rest lazily; keep the eager ones to what fills the first screen.
  • Rows are justified by assets/archive-layout.js: one fixed gutter (--tile-gap), and each row's height nudged around the target so it fills the width exactly; the last row stays at the target height. It loads as a plain script right after the list so the rows are sized before the first paint, and script.js calls it again after each filter, count change or resize. Remove it and the tiles keep the target height with a ragged right edge.

On the Work page the same frames also appear as <ol class="index" data-index>: one <li class="row"> per frame with the same data-k and data-cat. The preview that follows the pointer reuses the archive thumbnail with the matching data-k.

Projects (assets/projects.js and project.html)

Every commission and series is one entry in assets/projects.js; the comment at the top of that file lists the fields. project.html draws:

  • project.html: the list of all projects (the projects tab).
  • project.html?p=kiln: that project, led by its cover frame.
  • project.html?p=kiln&f=e4: the same project, led by frame e4. Archive tiles and Work index rows link this way.
  • An unknown p: a short note and the list.

Each frame is { k, w, h, cap, who?, col? }. k names its two files, assets/img/plate-<k>.webp (the large frame, w by h) and assets/img/t-<k>.webp (the archive thumbnail). col places it on the twelve-column book grid ("1 / -1" is full width); frames without one are laid out in alternating pairs and singles. Add prints: { note, sizes } to a project for a prints block; the example only sells prints from Upriver.

assets/project-page.js draws the page as a plain script straight after its root, before the first paint, so the lead image is ready for the view transition: the archive frame you opened carries view-transition-name: lead, the same as .lead-img, and supporting browsers grow one into the other (and back on Back). Without JavaScript the <noscript> block in project.html shows the Upriver project and the list of every project. When you add a project, add its frames' tiles to index.html and work.html too.

upriver.html is kept only so old links still work: it forwards to project.html?p=upriver with a meta refresh and, where JavaScript runs, keeps the f frame. Delete it on a new site.

About reel

<ul class="reel" data-reel> holds six buttons, each with data-cap for the caption line. Change the number of items freely; the chosen one opens to roughly three times the width of the others. Its link goes to project.html?p=upriver.

Contact

  • The chooser buttons carry data-type, matching the <option> values of the form's job-type select and the data-guide-for paragraphs. contact.html?type=prints arrives with that type chosen (the series page's print link uses it).
  • The form is not wired to anything. It validates name, email and message, then shows a confirmation saying nothing was sent. Connect it before going live: give the <form> an action for Formspree, Netlify Forms or your own endpoint and remove the submit handler in initEnquiry in assets/script.js, or replace that handler's final lines with a fetch() to your endpoint.

Phone menu

Below 900px the tabs give way to a red Menu block. It is a <details> element in the markup, so it opens without JavaScript; the script turns it into a button with aria-expanded that opens a full-height yellow sheet, moves focus into it, closes on Escape and returns focus.

Palettes

Five presets ship. The default lives in :root; the others are :root[data-palette="..."] blocks in assets/style.css that restate every colour token, its RGB triple and the display face. Set one on the finished site with <html data-palette="pink">, or copy a block's values into :root and delete the rest. The measured contrast for every pair is in the stylesheet comments.

Preset Flood Name block Display face Ink on flood
default (foam) #FFCC00 yellow #D7100A red Crimson Pro 12.2:1
coral #FF7A59 #1B2A8A navy Bricolage Grotesque 7.2:1
pink #FF9FCB #0D6345 green DM Serif Display 9.7:1
sky #86CCFF #C23A12 vermilion IBM Plex Mono 10.6:1
violet #BFA8FF #141414 ink Young Serif 9.0:1

Shared tokens:

Token Value Use
--paper #F3F1EC Page ground; ink on it 16.3:1
--ink #141414 Text, rules, buttons
--ink-dim #5E5B55 Captions and inactive controls, 6.0:1 on paper
--flood-dim per preset Small facts on the flood, at least 5.8:1

The name block is never text and never sits on the flood. When the header sits on a flood page top (the Contact page, class top--flood), the block turns ink. The live demo has a colour picker; it is preview chrome and is not part of the download.

Accessibility and Fallbacks

  • Without JavaScript every frame shows at the middle size, the filter, count and layout controls are hidden, the project page shows the Upriver project and the list of every project, and the menu opens as a native details element.
  • If the GSAP CDN is blocked, every control still works as an instant change.
  • Reduced motion keeps every control but changes instantly, and turns off the page transitions and the lead image morph.
  • The archive controls are buttons with aria-pressed; the frame count is announced in a polite live region. Archive images carry their captions as alt text, and the Work index lists every caption as text.
  • Focus rings show for keyboard use only (:focus-visible).

Dependencies

  • GSAP 3.15.0 with Flip (Home, Work, Project) and ScrollTrigger (all pages but Contact), from jsDelivr. Works with GSAP 3.12+.
  • No smooth-scrolling library: the archive pages are short at high density and read better with native scrolling.
  • Fonts: Crimson Pro and Public Sans, plus one display face per preset, self-hosted (see Images and Credits).

Images and Credits

Bundled photographs are WebP derivatives in assets/img/, cropped, resized and lightly desaturated, listed with their source URLs in assets/img-manifest.json: t-* are the archive thumbnails (about 7KB each), plate-* the large project frames (about 60KB each), about-bea and contact-studio the page photographs. All 170 files come to about 5.8MB; each page loads only what it shows. Rebuild them with bun scripts/build-template-assets.ts photography-portfolio-template --force (in the GSAP Vault repo), or replace a file with your own at the same name.

  • All photographs are by cottonbro studio on Pexels, under the Pexels licence; each source page is https://www.pexels.com/photo/<id>/ with the id from the manifest URL. The people in them are models and are not the fictional sitters, clients or anglers they are captioned as; the places are not the places named, and the fishing pictures were not taken on the Wye.
  • Replace them with your own work before going live: a photographer's portfolio has to show the photographer's pictures.
  • Fonts in assets/fonts/ are the latin files of Crimson Pro, Public Sans, Bricolage Grotesque, DM Serif Display, IBM Plex Mono and Young Serif, as served by Google Fonts, unmodified, under the SIL Open Font License 1.1; the copyright lines and licence texts are in assets/fonts/OFL.txt.

Bea Farrant, her clients, the publications, the sitters, the stylists, Dai and Owen Pryce, the Leadworks Gallery, the address, phone number, email and prices are fictional. Spike Island, Bristol, Rhayader, the River Wye and the other places named are real.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.