Run Python in Your Browser with PyScript

Soumil Jain Last Updated : 05 Jun, 2025
6 min read

In recent years, Python has become one of the most widely used programming languages. However, Python hasn’t played a large role when it comes to web development specifically, until now. PyScript is here to change that. It is a new framework that allows you to run Python code directly on your web browser using only HTML and Python code. Regardless of your experience level, it’s really simple to use PyScript to develop interactive web apps without knowing JavaScript. In this tutorial, you will learn about PyScript, what it is, how it works, and how to create your first browser-based Python app using it.

What is PyScript

PyScript is an open-source framework that bridges the gap between Python and the web. It lets you run Python code directly in your web browser. Allowing you to write interactive Python applications that run entirely on the client side, without needing a backend server. PyScript is like writing a web app with Python instead of JavaScript. You can build simple interactive web tools, dashboards, and more, all with Python.

Key Features of PyScript

  1. Python in Browser: You can write Python code inside <py-script> tags in your HTML file
  2. No Environment Setup: No need to install any additional libraries or tools. It runs in the browser.
  3. Interactivity with HTML: Easily integrates Python with HTML, CSS, and JavaScript.
  4. Powered by WebAssembly: Uses Pyodide(Python compiled to WebAssembly) to run Python in the browser.

How to Use PyScript for your WebApp?

Step 1: Visit the Official Website

Visit the official website. This is the where you can explore demos, documentation, and try it yourself.

PyScript Platform
Source: PyScript
PyScript Dashboard

Step 2: Set-up a Basic HTML File

To run PyScript, you’ll need a simple HTML file that has the required framework.

Example code:

<!DOCTYPE html>

<html lang="en">

  <head>

    <meta charset="UTF-8" />

    <title>My First PyScript App</title>

    <link rel="stylesheet" href="https://pyscript.net/latest/pyscript.css" />

    <script defer src="https://pyscript.net/latest/pyscript.js"></script>

  </head>

  <body>

    <h1>Hello from PyScript!</h1>

    <py-script>

      name = "PyScript"

      print(f"Hello, {name}! You are running Python in the browser.")

    </py-script>

  </body>

</html>

Step 3: Open the HTML file in a Browser.

By default, there will be 3 files:

main.py: Your Python code.

Index.html: The main web page that includes PyScript.

pyscript.toml: A configuration file listing any extra Python packages you
want to use.

Update the code files with the appropriate codes and start experimenting:

Tic-Tac-Toe

You can try PyScript Playground at PyScript examples to test code snippets directly in your browser.

Sample Codes

Hands-on with PyScript

Now that you are familiar with how the PyScript interface works, let us perform some hands-on with it.

We will build a two-player tic-tac-toe game.

Step 1: Update main.py 

Add the main.py file with the TicTacToe class, which contains the game logic, user interactions, and UI updates. It will use PyWeb to connect Python with HTML, making the game fully interactive within the browser.

Code:

from pyweb import pydom

class TicTacToe:

    def __init__(self):

        self.board = pydom["table#board"]

        self.status = pydom["h2#status"]

        self.console = pydom["script#console"][0]

        self.init_cells()

        self.init_winning_combos()

        self.new_game(...)

    def set_status(self, text):

        self.status.html = text

    def init_cells(self):

        self.cells = []

        for i in (0, 1, 2):

            row = []

            for j in (0, 1, 2):

                cell = pydom[f"div#cell{i}{j}"][0]

                assert cell

                row.append(cell)

            self.cells.append(row)

    def init_winning_combos(self):

        self.winning_combos = []

        # winning columns

        for i in (0, 1, 2):

            combo = []

            for j in (0, 1, 2):

                combo.append((i, j))

            self.winning_combos.append(combo)

        # winning rows

        for j in (0, 1, 2):

            combo = []

            for i in (0, 1, 2):

                combo.append((i, j))

            self.winning_combos.append(combo)

        # winning diagonals

        self.winning_combos.append([(0, 0), (1, 1), (2, 2)])

        self.winning_combos.append([(0, 2), (1, 1), (2, 0)])

    def new_game(self, event):

        self.clear_terminal()

        print('=================')

        print('NEW GAME STARTING')

        print()

        for i in (0, 1, 2):

            for j in (0, 1, 2):

                self.set_cell(i, j, "")

        self.current_player = "x"

experimenting        self.set_status(f'{self.current_player} playing...')

    def next_turn(self):

        winner = self.check_winner()

        if winner == "tie":

            self.set_status("It's a tie!")

            self.current_player = "" # i.e., game ended

            return

        elif winner is not None:

            self.set_status(f'{winner} wins')

            self.current_player = "" # i.e., game ended

            return

        if self.current_player == "x":

            self.current_player = "o"

        else:

            self.current_player = "x"

        self.set_status(f'{self.current_player} playing...')

    def check_winner(self):

        """

        Check whether the game as any winner.

        Return "x", "o", "tie" or None. None means that the game is still playing.

        """

        # check whether we have a winner

        for combo in self.winning_combos:

            winner = self.get_winner(combo)

            if winner:

                # highlight the winning cells

                for i, j in combo:

                    self.cells[i][j].add_class("win")

                return winner

        # check whether it's a tie

        for i in (0, 1, 2):

            for j in (0, 1, 2):

                if self.get_cell(i, j) == "":

                    # there is at least an empty cell, it's not a tie

                    return None # game still playing

        return "tie"

    def get_winner(self, combo):

        """

        If all the cells at the given points have the same value, return it.

        Else return "".

        Each point is a tuple of (i, j) coordinates.

        Example:

            self.get_winner([(0, 0), (1, 1), (2, 2)])

        """

        assert len(combo) == 3

        values = [self.get_cell(i, j) for i, j in combo]

        if values[0] == values[1] == values[2] and values[0] != "":

            return values[0]

        return ""

    def set_cell(self, i, j, value):

        assert value in ("", "x", "o")

        cell = self.cells[i][j]

        cell.html = value

        if "x" in cell.classes:

            cell.remove_class("x")

        if "o" in cell.classes:

            cell.remove_class("o")

        if "win" in cell.classes:

            cell.remove_class("win")

        if value != "":

            cell.add_class(value)

    def get_cell(self, i, j):

        cell = self.cells[i][j]

        value = cell.html

        assert value in ("", "x", "o")

        return value

    def click(self, event):

        i = int(event.target.getAttribute('data-x'))

        j = int(event.target.getAttribute('data-y'))

        print(f'Cell {i}, {j} clicked: ', end='')

        if self.current_player == "":

            print('game ended, nothing to do')

            return

        #

        value = self.get_cell(i, j)

        if value == "":

            print('cell empty, setting it')

            self.set_cell(i, j, self.current_player)

            self.next_turn()

        else:

            print(f'cell already full, cannot set it')

    def clear_terminal(self):

        self.console._js.terminal.clear()

    def toggle_terminal(self, event):

        hidden = self.console.parent._js.getAttribute("hidden")

        if hidden:

            self.console.parent._js.removeAttribute("hidden")

        else:

            self.console.parent._js.setAttribute("hidden", "hidden")

GAME = TicTacToe()

Step 2: Create a CSS file

Create a style.css file within the newly created assets folder to define the layout and the style for the Tic-Tac-Toe game. This will deal with the styling of the board, cells, and any status messages.

Code:

h1, h2 {

    font-family: 'Indie Flower', 'Comic Sans', cursive;

    text-align: center;

}

#board {

    font-family: 'Indie Flower', 'Comic Sans', cursive;

    position: relative;

    font-size: 120px;

    margin: 1% auto;

    border-collapse: collapse;

}

#board td {

    border: 4px solid rgb(60, 60, 60);

    width: 90px;

    height: 90px;

    vertical-align: middle;

    text-align: center;

    cursor: pointer;

}

#board td div {

    width: 90px;

    height: 90px;

    line-height: 90px;

    display: block;

    overflow: hidden;

    cursor: pointer;

}

.x {

    color: darksalmon;

    position: relative;

    font-size: 1.2em;

    cursor: default;

}

.o {

    color: aquamarine;

    position: relative;

    font-size: 1.0em;

    cursor: default;

}

.win {

    background-color: beige;

}

Step 3: Update index.html

Modifying the index.html file to reference the PyScript setup, load main.py, define the game board structure, and point to the style.css (from your assets folder) for the styling.

Code:

<!doctype html>

<html>

    <head>

        <!-- Recommended meta tags -->

        <meta charset="UTF-8">

        <meta name="viewport" content="width=device-width,initial-scale=1.0">

        <!-- PyScript CSS -->

        <link rel="stylesheet" href="https://pyscript.net/releases/2024.1.1/core.css">

        <!-- CSS for examples -->

        <link rel="stylesheet" href="./assets/css/examples.css" />

        <!-- This script tag bootstraps PyScript -->

        <script type="module" src="https://pyscript.net/releases/2024.1.1/core.js"></script>

        <!-- Custom CSS -->

        <link href="https://fonts.googleapis.com/css?family=Indie+Flower" rel="stylesheet">

        <link rel="stylesheet" href="./assets/css/tictactoe.css" />

        <!-- for splashscreen -->

        <style>

            #loading { outline: none; border: none; background: transparent }

        </style>

        <script type="module">

            const loading = document.getElementById('loading');

            addEventListener('py:ready', () => loading.close());

            loading.showModal();

        </script>

        <title>Tic Tac Toe</title>

        <link rel="icon" type="image/png" href="./assets/favicon.png" />

    </head>

    <body>

        <dialog id="loading">

            <h1>Loading...</h1>

        </dialog>

        <nav class="navbar" style="background-color: #000000">

            <div class="app-header">

                <a href="/">

                    <img src="./assets/logo.png" class="logo" />

                </a>

                <a class="title" href="" style="color: #f0ab3c">Tic Tac Toe</a>

            </div>

        </nav>

        <section class="pyscript">

            <h1>Tic-Tac-Toe</h1>

            <script type="py" src="./main.py" config="./pyscript.toml"></script>

            <table id="board">

                <tr>

                    <td><div id="cell00" data-x="0" data-y="0" class="cell" py-click="GAME.click"></div></td>

                    <td><div id="cell01" data-x="0" data-y="1" class="cell" py-click="GAME.click"></div></td>

                    <td><div id="cell02" data-x="0" data-y="2" class="cell" py-click="GAME.click"></div></td>

                <tr>

                    <td><div id="cell10" data-x="1" data-y="0" class="cell" py-click="GAME.click"></div></td>

                    <td><div id="cell11" data-x="1" data-y="1" class="cell" py-click="GAME.click"></div></td>

                    <td><div id="cell12" data-x="1" data-y="2" class="cell" py-click="GAME.click"></div></td>

                </tr>

                <tr>

                    <td><div id="cell20" data-x="2" data-y="0" class="cell" py-click="GAME.click"></div></td>

                    <td><div id="cell21" data-x="2" data-y="1" class="cell" py-click="GAME.click"></div></td>

                    <td><div id="cell22" data-x="2" data-y="2" class="cell" py-click="GAME.click"></div></td>

                </tr>

            </table>

            <h2 id="status"></h2>

            <button id="btn-new-game" py-click="GAME.new_game">New game</button>

            <button id="btn-toggle-terminal" py-click="GAME.toggle_terminal">Hide/show terminal</button>

            <div id="terminal" hidden="hidden">

                <script id="console" type="py" terminal></script>

            </div>

        </section>

    </body>

</html>

Step 4: Update pyscript.toml

Updating the pyscript.toml file with the necessary configuration needed by the app, including dependencies, file paths, etc. This ensures that PyScript knows how to load and run the Python code properly. Here are the contents of the pyscript.toml file for our Tic-Tac-Toe application:

Config:

name = "Tic Tac Toe"

description = "A Tic-Tac-Toe game written in PyScript that allows people to take turns."

Output:

Here you go with your first project on PyScript. 

Conclusion

Python is being used in Data Science, AI, Automation, and in education like never before. However, there hasn’t been a native home for Python on the web until now. PyScript has arrived and fuses the simplicity of Python with the accessibility of the web. It is still maturing, but it has already created lots of opportunities for developers, educators, and learners alike.

Data Scientist | AWS Certified Solutions Architect | AI & ML Innovator

As a Data Scientist at Analytics Vidhya, I specialize in Machine Learning, Deep Learning, and AI-driven solutions, leveraging NLP, computer vision, and cloud technologies to build scalable applications.

With a B.Tech in Computer Science (Data Science) from VIT and certifications like AWS Certified Solutions Architect and TensorFlow, my work spans Generative AI, Anomaly Detection, Fake News Detection, and Emotion Recognition. Passionate about innovation, I strive to develop intelligent systems that shape the future of AI.

Login to continue reading and enjoy expert-curated content.

Responses From Readers

Clear