The theming property is responsible for the look and feel of the app, as it allows to specify a large amount of properties:
- breakpoints;
- layout UI, defaults and constraints;
- header’s back link;
- progression;
- arrows and icons;
- themes.
Object breakpoints can be used to customize the breakpoint thresholds used throughout the UI. It is taking inspiration from Material Design’s Window-size classes.
The property is in enum ThBreakpoints and the value a number (in px). This value represents the max-width for each breakpoint.
If the value is set to null then the breakpoint is simply ignored and the next one (if any) will be used to construct a range.
For instance:
theming: {
...
breakpoints: {
[ThBreakpoints.compact]: 600,
[ThBreakpoints.medium]: 840,
[ThBreakpoints.expanded]: null,
[ThBreakpoints.large]: 1600,
[ThBreakpoints.xLarge]: null
}
}
Here we nullify the expanded breakpoint, which means it won’t be used. We will directly switch from medium to large.
Note that applying a number to xLarge will effectively constrain the range to this max value. In other words, if your screen/window is larger than this value, then all breakpoints will be false. This is why it will almost always be null.
The same ThBreakpoints thresholds are evaluated in two distinct contexts and exposed separately in Redux state:
breakpoint— resolved against the viewport (window) width via CSS media queries. Used for layout decisions that depend on the overall screen size.containerBreakpoint— resolved against the reader container’s width via a ResizeObserver. This is the equivalent of a CSS container query: if the reader is embedded in a panel or a smaller layout, its container breakpoint may differ from the window breakpoint.
Most UI components — collapsibility (breakpoint mode), running head format, footer display, paginated arrows — consume containerBreakpoint rather than breakpoint, so that they respond to the space actually available to the reader, not to the full viewport.
Object layout allows to configure:
uifor the layout of reflowable (scroll) and Fixed-Layout EPUBs;- the border
radiusof button icons and sheets/containers (inpx); - the
spacingof components (inpx) when applicable e.g. padding and spacing of sheets/containers; defaultsfor:dockingWidthor the size of dock panels by default (inpx);scrimor the CSS-valid string value for the bottom sheet’s underlaybackground– is overridable in each action’ssnappedpreference.
constraintsfor some components:- the
max-widthof the bottom sheet component – is overridable in each action’ssnappedpreference; - the
max-widthof the popover sheet component.
- the
For instance:
theming: {
...
layout: {
ui: {
reflow: ThLayoutUI.layered,
fxl: ThLayoutUI.layered
},
radius: 0,
spacing: 20,
defaults: {
dockingWidth: 300,
scrim: "rgba(0, 0, 0, 0.2)"
},
constraints: {
[ThSheetTypes.bottomSheet]: 600,
[ThSheetTypes.popover]: 400
}
}
}
This means:
- reflowable EPUBs will use
stackedlayout for paginated contents – top bar, contents, and bottom bar are stacked on top of one another –, andlayeredfor scrolled contents – contents occupy the full size of the viewport, nav bars are layered on top of it; - Fixed-Layout EPUBs will use
layeredlayout; - your actions’ triggers and containers won’t have any border radius;
- they’ll use
20pxas a reference for padding and their sections’ margins; - the default for the docking width is
300px; - the default scrim is color black with
20%alpha; - the bottom sheets are constrained to
600px, unless overridden for an action; - the popover sheets are constrained to
400px.
The header preference allows you to configure the header of the reader. It accepts the following properties:
backLink: The back link configurationrunningHead: The running head format
The backLink preference allows you to configure the back button in the header. It accepts the following properties:
href: (string) The URL to navigate to when the back button is clickedvisibility: Visibility of the back button. Ifalways, the back button will be always visible. Ifpartially, the back button will be hidden in immersive mode. It ispartiallyby default.variant?: Variant for the back link. Can be one of enumThBackLinkVariant:arrow: Shows an arrow icon (the default ifundefined)home: Shows a home iconlibrary: Shows a library/books iconcustom: Use withcontentto provide a custom icon
content?: Optional custom content for the back button whenvariantis set tocustom. Can be either:- An image with
{ type: "img"; src: string; alt?: string } - An SVG with
{ type: "svg"; content: string }with thecontentstring being the raw inline SVG markup
- An image with
For example:
theming: {
...
header: {
backLink: {
href: "/library",
variant: ThBackLinkVariant.custom,
visibility: "always",
content: {
type: "img",
src: "/path/to/custom-icon.png",
alt: "Back to Library"
}
}
}
}Or for a simple home button:
theming: {
...
header: {
backLink: {
href: "/",
variant: ThBackLinkVariant.home
}
}
}If backLink is undefined or null, then the back button will not be rendered.
The runningHead preference allows you to configure the running head format in the header. It accepts the following properties:
format: Format of the running head, with properties for both reflowable and fixed-layout EPUBs:reflow: Configuration for reflowable EPUBsfxl: Configuration for Fixed-Layout EPUBs
Both reflow and fxl share the same structure:
default: Default format configurationvariants: The format variant to use (e.g.,ThRunningHeadFormat.chapterorThRunningHeadFormat.title)displayInImmersive: Whether to show in immersive mode (default:true)displayInFullscreen: Whether to show in fullscreen mode (default:falsefor reflow,truefor fxl)
breakpoints(optional): Breakpoint-specific configurations[breakpoint]: Breakpoint name (e.g.,ThBreakpoints.compact)variants: Format variant for this breakpointdisplayInImmersive: Whether to show in immersive mode for this breakpointdisplayInFullscreen: Whether to show in fullscreen mode for this breakpoint
Available ThRunningHeadFormat variants:
title: Displays the publication titlechapter: Displays the current chapter/section titlenone: Hides the running head display
Example configuration:
theming: {
header: {
runningHead: {
format: {
reflow: {
default: {
variants: ThRunningHeadFormat.chapter,
displayInImmersive: true,
displayInFullscreen: false
},
breakpoints: {
[ThBreakpoints.compact]: {
variants: ThRunningHeadFormat.chapter,
displayInImmersive: false
}
}
},
fxl: {
default: {
variants: ThRunningHeadFormat.title,
displayInFullscreen: true
}
}
}
}
}
}The progression preference allows you to configure the progression display format. It accepts the following properties:
format: Format of the progression display, with properties for both reflowable and fixed-layout EPUBs:reflow: Configuration for reflowable EPUBsfxl: Configuration for Fixed-Layout EPUBs
Both reflow and fxl share the same structure:
default: Default format configurationvariants: The format variant(s) to use (e.g.,ThProgressionFormat.positionsPercentOfTotalor an array of formats)displayInImmersive: Whether to show in immersive modedisplayInFullscreen: Whether to show in fullscreen mode
breakpoints(optional): Breakpoint-specific configurations[breakpoint]: Breakpoint name (e.g.,ThBreakpoints.compact)variants: Format variant(s) for this breakpointdisplayInImmersive: Whether to show in immersive mode for this breakpointdisplayInFullscreen: Whether to show in fullscreen mode for this breakpoint
Available ThProgressionFormat variants:
positionsPercentOfTotal: "x-y of z (%)" (e.g. "25 of 50 (50%)")positionsOfTotal: "x-y of z" (e.g. "10-12 of 50")positions: "x-y" (e.g. "10-12")overallProgression: "x%" (e.g. "25%")positionsLeft: "x left in chapter" (e.g. "5 left in Chapter 1")readingOrderIndex: "x of y" (e.g. "1 of 5")resourceProgression: "x%" (e.g. "75%")progressionOfResource: "x% of y" (e.g. "75% of Chapter 1")none: Hides the progression display
Example configuration:
theming: {
progression: {
format: {
reflow: {
default: {
variants: [
ThProgressionFormat.positionsPercentOfTotal,
ThProgressionFormat.progressionOfResource
],
displayInImmersive: true,
displayInFullscreen: false
},
breakpoints: {
[ThBreakpoints.compact]: {
variants: [
ThProgressionFormat.positionsOfTotal,
ThProgressionFormat.resourceProgression
],
displayInImmersive: false
}
}
},
fxl: {
default: {
variants: [
ThProgressionFormat.positionsOfTotal,
ThProgressionFormat.overallProgression
],
displayInFullscreen: true
}
}
}
}
}Object arrow allows to configure the size and offset (in px), as well as the tooltipDelay (in ms), of the left and right arrow used to progress through paged resources.
Object icon allows to configure the size and tooltipOffset (in px), as well as the tooltipDelay (in ms), of icon buttons throughout the UI.
For instance:
theming: {
...
icon: {
size: 24,
tooltipOffset: 10,
tooltipDelay: 500
}
}
Will make the icons 24-pixel wide and tall, their tooltip will be placed 10 pixels from their reference, and their delay will be 500ms.
Object themes allows to customize the themes used and provided to users in Settings.
As a matter of fact, this user setting is created dynamically from these preferences.
Warning
Although not recommended, this means creating a theme took 4 steps if you were forking the project.
- add it to the
ThThemeKeysenum; - add its display string to
settings.themesin resources/locales; - add it to
reflowOrderand/orfxlOrder; - add and configure them in
themes.keys.
Your custom theme was then available without having to create or modify any component.
TBD: document new way of customizing theme keys.
You can set the display order of themes for formats/renditions the Reader supports:
reflowOrderfor reflowable EPUB;fxlOrderfor Fixed-Layout EPUB.
Note value "auto" is a special case that maps the theme to the OS’ preference (light or dark), it’s not a theme per se. It is required systemThemes is defined for it to work properly.
For instance, to provide light and dark mode (and the auto option mentioned above) for Fixed-Layout EPUB:
theming: {
theme: {
...
fxlOrder: [
"auto",
ThThemeKeys.light,
ThThemeKeys.dark
],
systemThemes: {
light: ThThemeKeys.light,
dark: ThThemeKeys.dark
}
}
}
If you want to provide an auto theme that maps to the OS’ preference (light or dark), you need to define systemThemes in your preferences.
For instance, to provide light and dark mode (and the auto option mentioned above) for Fixed-Layout EPUB:
theming: {
theme: {
...
fxlOrder: [
"auto",
ThThemeKeys.light,
ThThemeKeys.dark
],
systemThemes: {
light: ThThemeKeys.light,
dark: ThThemeKeys.dark
}
}
}
If you do not, the auto theme will not be rendered. In case it was previously selected, it will be set to the first theme in the fxlOrder array – excluding value auto.
The keys object contains the themes (key of ThThemeKeys enum as a property) and their tokens, whose value is a CSS-valid string:
background: the color of the backgroundtext: the color of the textlink: the color of linksvisited: the color of visited linkssubdue: the color of subdued elements such as bordersdisable: the color on:disabledhover: the color of the background on:hoveronHover: the color of the text on:hoverselect: the color of the background on::selectiononSelect: the color of the text on::selectionfocus: the color of the outline on:focus-visibleelevate: the drop shadow of containersimmerse: the opacity of immersive mode (value in the range[0...1]as a string)