Theming & styling
A custom or dark Material UI theme, styling from the host page, or Shadow DOM isolation.
Custom theme
The view takes a Material UI theme on its configuration block. Four named
palette colors — primary, secondary, tertiary, quaternary — drive most
of the chrome: toolbars, buttons, highlights. The DNA base colors in the
sequence display are configured separately.
Full options are in the theming guide.
View source — 42 lines
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
export default function WithCustomTheme() {
const state = useCreateViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
tracks: [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
],
configuration: {
theme: {
palette: {
primary: { main: '#311b92' },
secondary: { main: '#0097a7' },
tertiary: { main: '#f57c00' },
quaternary: { main: '#d50000' },
bases: {
A: { main: '#98FB98' },
C: { main: '#87CEEB' },
G: { main: '#DAA520' },
T: { main: '#DC143C' },
},
},
},
},
})
return <JBrowseLinearGenomeView viewState={state} />
}Dark theme
configuration.theme.palette.mode: 'dark' renders in dark mode. JBrowse is
tuned to look reasonable under both palettes.
To override individual colors, see the custom theme example.
View source — 35 lines
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
export default function WithDarkTheme() {
const state = useCreateViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
tracks: [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
],
configuration: {
theme: {
palette: {
mode: 'dark',
primary: { main: '#333' },
secondary: { main: '#444' },
},
},
},
})
return <JBrowseLinearGenomeView viewState={state} />
}Styling from outside the component
The view inherits CSS from its host — there is no shadow-DOM isolation in the default rendering path, so wrapping it in a styled container just composes.
For the opposite, guaranteed isolation from a host page’s global styles, render it inside a Shadow DOM instead.
View source — 31 lines
import {
JBrowseLinearGenomeView,
useCreateViewState,
} from '@jbrowse/react-linear-genome-view2'
export default function WithOutsideStyling() {
const state = useCreateViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
tracks: [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
],
location: 'ctgA:1105..1221',
})
return (
<div style={{ textAlign: 'center', fontFamily: 'monospace' }}>
<JBrowseLinearGenomeView viewState={state} />
</div>
)
}Package as a custom element
Mounting the view inside a Shadow DOM and registering it with
@r2wc/react-to-web-component
turns it into one HTML tag a host page can drop in with no React of its own:
<jbrowse-linear-view></jbrowse-linear-view>
Shadow DOM is what makes that safe to hand out: styles can’t leak in from the host page or out into it, which matters in a CMS, a LIMS, or any page whose global CSS you don’t control.
The component needs no special configuration. The one thing you must do is point
Material UI’s emotion cache and its portal containers (menus, tooltips,
dialogs) at a target inside the shadow root — otherwise MUI appends them to
document.body, outside the boundary, where the cache’s styles don’t reach and
they render unstyled.
To compose with parent styles rather than isolate from them, see styling from outside.
View source — 107 lines
import { createElement, useEffect, useRef, useState } from 'react'
import createCache from '@emotion/cache'
import { CacheProvider } from '@emotion/react'
import {
JBrowseLinearGenomeView,
createViewState,
destroyViewState,
} from '@jbrowse/react-linear-genome-view2'
import r2wc from '@r2wc/react-to-web-component'
import { createPortal } from 'react-dom'
import type { EmotionCache } from '@emotion/cache'
import type { ViewModel } from '@jbrowse/react-linear-genome-view2'
const ShadowComponent = () => {
const nodeRef = useRef<HTMLDivElement>(null)
const nodeForPinRef = useRef(null)
const [rootNode, setRootNode] = useState<ShadowRoot>()
const [cacheNode, setCacheNode] = useState<EmotionCache>()
const [config, setConfig] = useState<ViewModel>()
useEffect(() => {
if (!nodeRef.current) {
return
}
const root = nodeRef.current.attachShadow({ mode: 'open' })
setRootNode(root)
setCacheNode(
createCache({ key: 'react-shadow', prepend: true, container: root }),
)
const engine = createViewState({
assembly: {
name: 'volvox',
uri: 'https://jbrowse.org/genomes/volvox/volvox.2bit',
},
tracks: [
{
type: 'FeatureTrack',
trackId: 'volvox_gff3',
name: 'Volvox genes',
assemblyNames: ['volvox'],
adapter: {
type: 'Gff3TabixAdapter',
uri: 'https://jbrowse.org/code/jb2/main/test_data/volvox/volvox.sort.gff3.gz',
},
},
],
location: 'ctgA:1105..1221',
configuration: {
theme: {
palette: { primary: { main: '#4400a6' } },
components: {
MuiPopover: {
defaultProps: { container: () => nodeForPinRef.current },
},
MuiPopper: {
defaultProps: { container: () => nodeForPinRef.current },
},
MuiTooltip: {
defaultProps: {
slotProps: {
popper: { container: () => nodeForPinRef.current },
},
},
},
MuiModal: {
defaultProps: { container: () => nodeForPinRef.current },
},
MuiMenu: {
defaultProps: { container: () => nodeForPinRef.current },
},
},
},
},
})
// eslint-disable-next-line @eslint-react/set-state-in-effect -- shadow DOM setup requires setState in effect
setConfig(engine)
// the engine is not owned by React: unmounting the custom element leaves
// its RPC worker threads and its autoruns running unless it is torn down
return () => {
destroyViewState(engine)
}
}, [])
return (
<div ref={nodeRef}>
{rootNode && config && cacheNode
? createPortal(
<CacheProvider value={cacheNode}>
<JBrowseLinearGenomeView viewState={config} />
<div ref={nodeForPinRef} />
</CacheProvider>,
rootNode,
)
: null}
</div>
)
}
const JBrowseCustom = () => createElement(ShadowComponent, null, null)
export default function ShadowDOMOneLinearGenomeView() {
if (customElements.get('jbrowse-linear-view') === undefined) {
customElements.define('jbrowse-linear-view', r2wc(JBrowseCustom))
}
// @ts-expect-error
return <jbrowse-linear-view />
}