# Pages & Routing | 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]

# *Pages* & Routing

## [Page](/documentation/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 (defaults to `language` argument of `run` command)
: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()
````

[See more →](/documentation/page)

## [Page Layout](/documentation/page_layout)

With [`ui.header`](page_layout#reference_for_ui_header),
[`ui.footer`](page_layout#reference_for_ui_footer),
[`ui.left_drawer`](page_layout#reference_for_ui_left_drawer) and
[`ui.right_drawer`](page_layout#reference_for_ui_right_drawer) you can add additional layout elements to a page.

- The `fixed` argument controls whether the element should scroll or stay fixed on the screen.
- The `top_corner` and `bottom_corner` arguments indicate whether a drawer should expand to the top or bottom of the page.
- See <https://quasar.dev/layout/header-and-footer> and <https://quasar.dev/layout/drawer> for more information about possible props.

With [`ui.page_sticky`](page_layout#reference_for_ui_page_sticky) you can place an element "sticky" on the screen.

- The `position`, `x_offset` and `y_offset` arguments control the position of the sticky element.
- See <https://quasar.dev/layout/page-sticky> for more information.

With [`ui.page_scroller`](page_layout#reference_for_ui_page_scroller) you can add a scroll-to-top button to the page (*added in version 3.3.0*).

- Shares the basic arguments with [`ui.page_sticky`](page_layout#reference_for_ui_page_sticky).
- The `scroll_offset`, `duration` and `reverse` arguments control the behavior.
- See <https://quasar.dev/layout/page-scroller> for more information.

main.py

[Button]

````python
from nicegui import ui

@ui.page('/page_layout')
def page_layout():
    ui.label('CONTENT')
    [ui.label(f'Line {i}') for i in range(100)]
    with ui.header(elevated=True).style('background-color: #3874c8').classes('items-center justify-between'):
        ui.label('HEADER')
        ui.button(on_click=lambda: right_drawer.toggle(), icon='menu').props('flat color=white')
    with ui.left_drawer(top_corner=True, bottom_corner=True).style('background-color: #d7e3f4'):
        ui.label('LEFT DRAWER')
    with ui.right_drawer(fixed=False).style('background-color: #ebf1fa').props('bordered') as right_drawer:
        ui.label('RIGHT DRAWER')
    with ui.footer().style('background-color: #3874c8'):
        ui.label('FOOTER')
    with ui.page_sticky(position='bottom-left', x_offset=20, y_offset=20):
        ui.button('Sticky Button', on_click=lambda: ui.notify('Sticky Button Clicked'))
    with ui.page_scroller(position='bottom-right', x_offset=20, y_offset=20):
        ui.button('Scroll to Top')

@ui.page('/')
def page():
    ui.link('show page with fancy layout', page_layout)

ui.run()
````

[See more →](/documentation/page_layout)

## [Sub Pages](/documentation/sub_pages)

Sub pages provide URL-based navigation between different views.
This allows you to easily build a single page application (SPA).
The `ui.sub_pages` element itself functions as the container for the currently active sub page.
You only need to provide the routes for each view builder function.
NiceGUI takes care of replacing the content without triggering a full page reload when the URL changes.

main.py

[Button]

````python
from nicegui import ui
from uuid import uuid4

def root():
    ui.label(f'This ID {str(uuid4())[:6]} changes only on reload.')
    ui.separator()
    ui.sub_pages({'/': main, '/other': other})

def main():
    ui.label('Main page content')
    ui.link('Go to other page', '/other')

def other():
    ui.label('Another page content')
    ui.link('Go to main page', '/')

ui.run(root)
````

[See more →](/documentation/sub_pages)

## Script Mode

While generally you would either use `@ui.page` decorators or a root function to create pages,
it is cumbersome when making quick prototypes or demos.
In such cases, you can use "script mode" by simply writing code at the top level of a script.
The code will be executed once per client connection, and the interface will be created for that client.

main.py

[Button]

````python
from nicegui import ui

ui.label('No @ui.page, no root function, but still a working page!')

ui.run()
````

Note: Many of the demos in this documentation are written in script mode for conciseness.

## Parameter injection

Thanks to FastAPI, a page function accepts optional parameters to provide
[path parameters](https://fastapi.tiangolo.com/tutorial/path-params/),
[query parameters](https://fastapi.tiangolo.com/tutorial/query-params/) or the whole incoming
[request](https://fastapi.tiangolo.com/advanced/using-request-directly/) for accessing
the body payload, headers, cookies and more.

main.py

[Button]

````python
from nicegui import ui

@ui.page('/icon/{icon}')
def icons(icon: str, amount: int = 1):
    ui.label(icon).classes('text-h3')
    with ui.row():
        [ui.icon(icon).classes('text-h3') for _ in range(amount)]

@ui.page('/')
def page():
    ui.link('Star', '/icon/star?amount=5')
    ui.link('Home', '/icon/home')
    ui.link('Water', '/icon/water_drop?amount=3')

ui.run()
````

## [Page title](/documentation/page_title)

Set the page title for the current client.

:title: page title

main.py

[Button]

````python
from nicegui import ui

ui.button('Change page title', on_click=lambda: ui.page_title('New Title'))

ui.run()
````

[See more →](/documentation/page_title)

## [Status code](/documentation/status_code)

Set the HTTP status code for the current page response.
Must be called during page building, before the response is sent to the client.

*Added in version 3.10.0*

:code: HTTP status code (e.g. 200, 404, 503)

main.py

[Button]

````python
from nicegui import ui

@ui.page('/teapot')
def teapot_page():
    ui.status_code(418)
    ui.label("I'm a teapot")

@ui.page('/')
def page():
    ui.link('Visit the teapot page', '/teapot')

ui.run()
````

[See more →](/documentation/status_code)

## [Navigation functions](/documentation/navigate)

These functions allow you to navigate within the browser history and to external URLs.

*Added in version 2.0.0*

main.py

[Button]

````python
from nicegui import ui

with ui.row():
    ui.button('Back', on_click=ui.navigate.back)
    ui.button('Forward', on_click=ui.navigate.forward)
    ui.button('Reload', on_click=ui.navigate.reload)
    ui.button(icon='savings',
              on_click=lambda: ui.navigate.to('https://github.com/sponsors/zauberzeug'))

ui.run()
````

[See more →](/documentation/navigate)

## ui.open


    The `ui.open` function has been removed.
    Use [`ui.navigate.to`](navigate#ui_navigate_to_(formerly_ui_open)) instead.


## [Download functions](/documentation/download)

These functions allow you to download files, URLs or raw data.

*Added in version 2.14.0*

main.py

[Button]

````python
from nicegui import ui

ui.button('Local file', on_click=lambda: ui.download.file('main.py'))
ui.button('From URL', on_click=lambda: ui.download.from_url('/logo.png'))
ui.button('Content', on_click=lambda: ui.download.content('Hello World', 'hello.txt'))

ui.run()
````

[See more →](/documentation/download)

## Add a directory of static files

`add_static_files()` makes a local directory available at the specified endpoint, e.g. `'/static'`.
This is useful for providing local data like images to the frontend.
Otherwise the browser would not be able to access the files.
Do only put non-security-critical files in there, as they are accessible to everyone.

To make a single file accessible, you can use `add_static_file()`.
For media files which should be streamed, you can use `add_media_files()` or `add_media_file()` instead.

:url_path: string that starts with a slash "/" and identifies the path at which the files should be served
:local_directory: local folder with files to serve as static content
:follow_symlink: whether to follow symlinks (default: False)
:max_cache_age: value for max-age set in Cache-Control header (*added in version 2.8.0*)

main.py

[Button]

````python
from nicegui import app, ui

app.add_static_files('/examples', 'examples')
ui.label('Some NiceGUI Examples').classes('text-h5')
ui.link('AI interface', '/examples/ai_interface/main.py')
ui.link('Custom FastAPI app', '/examples/fastapi/main.py')
ui.link('Authentication', '/examples/authentication/main.py')

ui.run()
````

## Add directory of media files

`add_media_files()` allows a local files to be streamed from a specified endpoint, e.g. `'/media'`.
This should be used for media files to support proper streaming.
Otherwise the browser would not be able to access and load the the files incrementally or jump to different positions in the stream.
Do only put non-security-critical files in there, as they are accessible to everyone.

To make a single file accessible via streaming, you can use `add_media_file()`.
For small static files, you can use `add_static_files()` or `add_static_file()` instead.

:url_path: string that starts with a slash "/" and identifies the path at which the files should be served
:local_directory: local folder with files to serve as media content

main.py

[Button]

````python
import httpx
from nicegui import app, ui
from pathlib import Path

media = Path('media')
media.mkdir(exist_ok=True)
r = httpx.get('https://cdn.coverr.co/videos/coverr-cloudy-sky-2765/1080p.mp4')
(media  / 'clouds.mp4').write_bytes(r.content)
app.add_media_files('/my_videos', media)
ui.video('/my_videos/clouds.mp4')

ui.run()
````

## Add HTML to the page

You can add HTML to the page by calling `ui.add_head_html` or `ui.add_body_html`.
This is useful for adding custom CSS styles or JavaScript code.

main.py

[Button]

````python
from nicegui import ui

ui.add_head_html('''
    <style>
        .my-red-label {
            color: Crimson;
            font-weight: bold;
        }
    </style>
''')
ui.label('RED').classes('my-red-label')

ui.run()
````

## API Responses

NiceGUI is based on [FastAPI](https://fastapi.tiangolo.com/).
This means you can use all of FastAPI's features.
For example, you can implement a RESTful API in addition to your graphical user interface.
You simply import the `app` object from `nicegui`.
Or you can run NiceGUI on top of your own FastAPI app by using `ui.run_with(app)` instead of starting a server automatically with `ui.run()`.

You can also return any other FastAPI response object inside a page function.
For example, you can return a `RedirectResponse` to redirect the user to another page if certain conditions are met.
This is used in our [authentication demo](https://github.com/zauberzeug/nicegui/tree/main/examples/authentication/main.py).

main.py

[Button]

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

@app.get('/random/{max}')
def generate_random_number(max: int):
    return {'min': 0, 'max': max, 'value': random.randint(0, max)}

@ui.page('/')
def page():
    max = ui.number('max', value=100)
    ui.button('generate random number',
              on_click=lambda: ui.navigate.to(f'/random/{max.value:.0f}'))

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)