aw codes icon logo
Docs Hub

Search across every project.

Focus Switch project

Generate consistent documentation screenshots from Laravel Workbench applications using Playwright.

0.x Current

On this page

Configuration

Focus has no Laravel configuration file. Everything lives in a focus.php manifest at the root of your repository, because the repository, not an application, owns its screenshots.

The manifest

focus.php returns a ScreenshotSuite:

<?php

use Awcodes\Focus\Screenshot;
use Awcodes\Focus\ScreenshotSuite;

return ScreenshotSuite::make()
    ->screenshots([
        Screenshot::make('dashboard')
            ->visit('/admin')
            ->viewport(),
    ]);

To use a different file, pass --config:

vendor/bin/focus --config=custom-focus.php

Focus validates the whole manifest before opening a browser and reports every problem at once, with the manifest line where it can. These are errors:

  • a screenshot name that is not lowercase kebab-case (^[a-z0-9]+(-[a-z0-9]+)*$), such as Editor or brick_picker;
  • two screenshots with the same name;
  • a screenshot that never calls visit();
  • more than one capture mode on a screenshot (see Capture modes);
  • padding() or minSize() on a screenshot that does not use focus();
  • invalid arguments, such as a negative padding or a minSize() width without a height.

With cards, these are also errors:

  • a card name that is not lowercase kebab-case, or that is already used by a screenshot or another card;
  • cards() without cardTemplates(), or a GitHub template source that cannot be parsed;
  • a card that uses a screenshot the suite does not define, or one not captured in every theme the card renders.

A suite needs at least one screenshot or card, so a manifest with only cards is valid.

Suite settings

Most settings can be set on the suite for every screenshot, or on an individual screenshot:

Method Purpose Default
themes([...]) Themes to capture [Theme::Light, Theme::Dark]
viewportSize(...) Browser viewport 1440x1000 (Viewport::Desktop)
padding(int) Context around a focus() subject, in CSS pixels 32
minSize(...) Minimum focus() crop none
scale(int|float) Output pixel density 2
allowAnimations() Keep CSS animations and transitions animations disabled
locale(string) Browser locale en-US
timezone(string) Browser timezone UTC
freezeTime(...) Fixed browser clock, or false for the real clock 2026-01-01 09:00:00
mask(...) Selectors to cover at capture time none
hide(...) Selectors to hide at capture time, keeping layout none
maskColor(string) Mask fill colour #FF00FF
keepInteractionState() Keep hover and focus left by interactions cleared before capture

These settings are suite-level only:

Method Purpose Default
outputPath(string) Where PNGs are written, relative to the repository root unless absolute docs/assets
baseUrl(string) Use an already-running application instead of starting Workbench Focus starts Workbench
login(...) Sign in through a login form Workbench development account
withoutLogin() Skip authentication —
authenticateUsing(...) Custom authentication —
reuseSession(bool) Reuse the previous run's signed-in session with login() true
timeout(int) Timeout in milliseconds for interactions and selectors 15000
beforeEach(Closure) Callback before every screenshot —
cards([...]) Share images to render (see Cards) none
cardTemplates(string) Card template directory: a path or a GitHub reference required with cards()
cardOutputPath(string) Where cards are written art

Authentication and the server are covered in Workbench integration.

Setting precedence

Every setting resolves in the same order, most specific first:

Screenshot → ScreenshotSuite → package default

For example, a suite that sets ->scale(1) produces 1× images, except for a screenshot that sets ->scale(2) itself:

return ScreenshotSuite::make()
    ->scale(1)
    ->themes([Theme::Light])
    ->screenshots([
        Screenshot::make('overview')->visit('/admin'),                 // 1×, light
        Screenshot::make('detail')->visit('/admin')->scale(2)          // 2×, light
            ->focus('[data-focus="detail"]'),
    ]);

Masks and hidden selectors are the exception: suite and screenshot values are combined rather than replaced.

Suite-level padding() and minSize() apply only to focus() screenshots and are ignored for viewport() and fullPage() ones.

CLI options take precedence over the manifest where they overlap: --base-url overrides baseUrl(). The --only and --theme filters only narrow what the manifest defines; --theme=dark never produces a dark capture for a screenshot restricted to Theme::Light.

Output location

Screenshots are written to docs/assets by default. Change it at suite level:

return ScreenshotSuite::make()
    ->outputPath('resources/screenshots')
    ->screenshots([...]);

Cards are written to art by default; change it with cardOutputPath().

The file naming scheme, atomic writes, and orphan handling are covered in CLI.