# ui.page | NiceGUI

[Button: icon:menu]

[](/)

[Installation](/#installation)

[Features](/#features)

[Demos](/#demos)

[Documentation](/documentation)

[Examples](/examples)

[Why?](/#why)

[Button]

[Button]

[](https://github.com/zauberzeug/nicegui/)

[Button: icon:more_vert]

# ui.*page*

## Page

This decorator marks a function to be a page builder.
Each user accessing the given route will see a new instance of the page.
This means it is private to the user and not shared with others.

Notes:

- The name of the decorated function is unused and can be anything.
- The page route is determined by the `path` argument and registered globally.
- The decorator does only work for free functions and static methods.
  Instance methods or initializers would require a `self` argument, which the router cannot associate.
  See `our modularization example <https://github.com/zauberzeug/nicegui/tree/main/examples/modularization/>`_
  for strategies to structure your code.

:path: route of the new page (path must start with '/')
:title: optional page title
:viewport: optional viewport meta tag content
:favicon: optional relative filepath or absolute URL to a favicon (default: `None`, NiceGUI icon will be used)
:dark: whether to use Quasar's dark mode (defaults to `dark` argument of `run` command)
:language: language of the page, used for Quasar elements and the ``lang`` attribute of the ``html`` tag
    (defaults to ``language`` argument of ``run`` command, *updated in version 3.14.0*: can be ``None`` to omit the ``lang`` attribute)
:response_timeout: maximum time for the decorated function to build the page (default: 3.0 seconds)
:reconnect_timeout: maximum time the server waits for the browser to reconnect (defaults to `reconnect_timeout` argument of `run` command))
:markdown: whether to serve a Markdown representation when a client sends ``Accept: text/markdown``
    (experimental, defaults to `markdown` argument of `run` command, *added in version 3.11.0*)
:api_router: APIRouter instance to use, can be left `None` to use the default
:kwargs: additional keyword arguments passed to FastAPI's @app.get method

main.py

[Button]

````python
from nicegui import ui

@ui.page('/other_page')
def other_page():
    ui.label('Welcome to the other side')

@ui.page('/dark_page', dark=True)
def dark_page():
    ui.label('Welcome to the dark side')

@ui.page('/')
def page():
    ui.link('Visit other page', other_page)
    ui.link('Visit dark page', dark_page)

ui.run()
````

## Pages with Path Parameters

Page routes can contain parameters like [FastAPI](https://fastapi.tiangolo.com/tutorial/path-params/).
If type-annotated, they are automatically converted to bool, int, float and complex values.
If the page function expects a `request` argument, the request object is automatically provided.
An optional `client` argument provides access to the websocket connection, layout, etc.
(same as `ui.context.client`).

main.py

[Button]

````python
from nicegui import ui

@ui.page('/repeat/{word}/{count}')
def repeat(word: str, count: int):
    ui.label(word * count)

@ui.page('/')
def page():
    ui.link('Say hi to Santa!', '/repeat/Ho! /3')

ui.run()
````

## Wait for Client Connection

To wait for a client connection, you can await `ui.context.client.connected()`.
All code below that statement is executed after the websocket connection between server and client has been established.

For example, this allows you to run JavaScript commands; which is only possible with a client connection (see [#112](https://github.com/zauberzeug/nicegui/issues/112)).
Also it is possible to do async stuff while the user already sees some content.

main.py

[Button]

````python
import asyncio
from nicegui import ui

@ui.page('/wait_for_connection')
async def wait_for_connection():
    ui.label('This text is displayed immediately.')
    await ui.context.client.connected()
    await asyncio.sleep(2)
    ui.label('This text is displayed 2 seconds after the page has been fully loaded.')

@ui.page('/')
def page():
    ui.link('wait for connection', wait_for_connection)

ui.run()
````

## Multicasting

The content on a page is private to the client (the browser tab) and has its own local element context.
If you want to send updates to _all_ clients of a specific page, you can use the `app.clients` iterator.
This is useful for modifying UI elements from a background process or from other pages.

*Added in version 2.7.0*

main.py

[Button]

````python
from nicegui import app, ui

@ui.page('/multicast_receiver')
def multicast_receiver():
    ui.label('This page will show messages from the index page.')

def send(message: str):
    for client in app.clients('/multicast_receiver'):
        with client:
            ui.notify(message)

@ui.page('/')
def page():
    ui.button('Send message', on_click=lambda: send('Hi!'))
    ui.link('Open receiver', '/multicast_receiver', new_tab=True)

ui.run()
````

## Modularize with APIRouter

You can use the NiceGUI specialization of
[FastAPI's APIRouter](https://fastapi.tiangolo.com/tutorial/bigger-applications/?h=apirouter#apirouter)
to modularize your code by grouping pages and other routes together.
This is especially useful if you want to reuse the same prefix for multiple pages.
The router and its pages can be neatly tugged away in a separate module (e.g. file) and
the router is simply imported and included in the main app.
See our [modularization example](https://github.com/zauberzeug/nicegui/blob/main/examples/modularization/api_router_example.py)
for a multi-file app structure using an API router.

main.py

[Button]

````python
from nicegui import APIRouter, app, ui

router = APIRouter(prefix='/sub-path')

@router.page('/')
def sub_page():
    ui.label('This is content on /sub-path')

@router.page('/sub-sub-path')
def sub_sub_page():
    ui.label('This is content on /sub-path/sub-sub-path')

@ui.page('/')
def page():
    ui.link('Visit sub-path', '/sub-path')
    ui.link('Visit sub-sub-path', '/sub-path/sub-sub-path')

app.include_router(router)

ui.run()
````

**Nice**GUI

The Python UI framework that shows up in your browser.

[](https://github.com/zauberzeug/nicegui/)

[](https://discord.gg/TEpFeAaF4f)

[](https://www.reddit.com/r/nicegui/)

Resources

[Documentation](/documentation)

[Examples](/examples)

[LLM reference](/llms.txt)

[GitHub](https://github.com/zauberzeug/nicegui/)

[PyPI](https://pypi.org/project/nicegui/)

Community

[Discussions](https://github.com/zauberzeug/nicegui/discussions)

[Discord](https://discord.gg/TEpFeAaF4f)

[Reddit](https://www.reddit.com/r/nicegui/)

[Contributing](https://github.com/zauberzeug/nicegui/blob/main/CONTRIBUTING.md)

[Sponsors](https://github.com/sponsors/zauberzeug)

Legal

[Imprint](/imprint_privacy#imprint)

[Privacy](/imprint_privacy#privacy)

Made with NiceGUI by [Zauberzeug](https://zauberzeug.com)

© 2026 [Zauberzeug GmbH](https://zauberzeug.com)