Skip to main content

Interface 1.2+ Slots

Use Slots to render contents of your page the other areas of the layout.

Every Contember component can access data binding context and render it to the page but this composition is limited to how the content might be rendered as is.

For example you can't render a persist button to save the form in the header from the page because it is nested within the descendants of the page that is rendered in the layout.

To overcome this limitation slots provide a way to escape and render to other parts of the layout easily, even the parts that are not directly descendants of the page, e.g. header or footer of the layout.

What are slots?

Layout slots are React components that can be used to render content in predefined places of the layout. Each SlotSource can render (append) its children to the target as createPortal() would do.

This is useful when you want to render a button in the footer, header, sidebar or any other place of the layout that is not directly accessible from within inside of the rendered page.

You can use these already pre-build common slots that are typical for all Contember layouts and layout specific slots. Common slots are:

  • Actions – Slot for rendering page actions (usually in the header)
  • Back – Slot for rendering back button (usually in the header near title)
  • Logo – Slot for rendering logo (usually in the header)
  • Navigation – Slot for rendering navigation (usually in the sidebar)
  • Sidebar – Slot for rendering sidebar (usually in the right side of the layout)
  • Title – Slot for rendering page title (usually in the header)
  • Profile – Slot for rendering profile with dropdown, logout button (usually in the header)
  • Switchers – Slot for rendering switchers for e.g. language or color scheme (usually in the header)

You are not tied to using our predefined layouts or slots. You are free to create your own layouts with custom slots and use them in your pages just like the built-in ones.

Creating your own slots

Slots are made of two pair components. One component is used to render content to the portal (source) and the other component is used to render the content from the portal (target).

To create your slot you need to create two components:

  • SlotSource – React component that will be used to render content from pages
  • SlotTarget – React component that will be used in place where you want to render content
tip

You can check if there is any content to render and draw SlotTarget components on demand using the useTargetsIfActiveFactory or useHasActiveSlotsFactory hooks.

admin/components/Slots.tsx
import { CommonSlotSources, CommonSlotTargets, Slots, commonSlots } from '@contember/layout'
import { useDocumentTitle } from '@contember/react-utils'
import { memo } from 'react'

// 1. Defining your own slots:
const [
mySlots, // 👈 same array as the argument (for convenience)
MySlotSources, // 👈 components that "portal" their children to other DOM nodes
MySlotTargets, // 👈 components that "receive" children from slot sources
] = Slots.createSlotComponents([
'Subtitle',
])

// 2. Exporting your slots together with the common ones:
export const slots = [
...commonSlots,
...mySlots,
]

export const SlotSources = {
...CommonSlotSources,
...MySlotSources,
}

export const SlotTargets = {
...CommonSlotTargets,
...MySlotTargets,
}

export const Title = memo<{ children: string | null | undefined }>(({ children }) => {
// Hook that will update the document title in browser tab:
useDocumentTitle(children)

return (
// Title is already defined in CommonSlotSources, so we can use it:
<SlotSources.Title>{children}</SlotSources.Title>
)
})

Rendering page content to a slot source

By wrapping the content in the slot source component, instead of rendering it as an immediate descendants of the page, you will create a portal that will render the content to the target component.

admin/pages/example.tsx
import { SlotSources } from '../components/Slots

export default () => (
<SlotSources.Subtitle>
<p>This is a subtitle that will be rendered somewhere else</p>
</SlotSources.Subtitle>
)

Placing slot targets in the layout

  • Slots.useTargetsIfActiveFactory() – Hook that returns a function that returns listed Slot target elements if there is any content to render, otherwise it returns null. If you provide the second argument it will be returned instead of creating the target elements automatically giving you manual control over how the target elements are rendered.
  • Slots.useHasActiveSlotsFactory() – Hook that returns a function that returns true if there is any content to render, otherwise it returns false which gives you even more control to compose layout.
admin/components/Layout.tsx
import { SlotSources } from '../components/SlotTargets'

export const Layout = ({ children }: React.PropsWithChildren) => {
const slotTargetsIfActive = Slots.useTargetsIfActiveFactory(SlotTargets)
const hasActiveSlots = Slots.useHasActiveSlotsFactory(SlotTargets)

return (
<div className="layout">
{slotTargetsIfActive(['Back', 'Title'], (
<>
<Back />
<Title as="h1" />
</>
))}

{slotTargetsIfActive(['Subtitle'])}

{hasActiveSlots('Actions') && (
<div className="layout__actions">
<Actions />
</div>
)}

{children}
</div>
)
}
note

Slots use React.createPortal under the hood. But Slots gives you extra tools to control how your layout behaves, when there is no content to render.

Context Providers

Using slots is optional. Their respective providers are included in our project templates in the application entry point. Once you decide to go your own way just remove the <Slots.Provider> from the index.tsx file.