<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Brooke Myers</title>
    <description>The latest articles on DEV Community by Brooke Myers (@bcm628).</description>
    <link>https://dev.to/bcm628</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F532732%2F93ed4a26-abde-4a4d-8b8a-3aabd7f066ca.JPG</url>
      <title>DEV Community: Brooke Myers</title>
      <link>https://dev.to/bcm628</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bcm628"/>
    <language>en</language>
    <item>
      <title>The Best Python Web App Frameworks in 2025</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Tue, 22 Jul 2025 13:48:46 +0000</pubDate>
      <link>https://dev.to/anvil/the-best-python-web-app-frameworks-in-2025-48dk</link>
      <guid>https://dev.to/anvil/the-best-python-web-app-frameworks-in-2025-48dk</guid>
      <description>&lt;h1&gt;
  
  
  The Top 5 Python GUI Builders
&lt;/h1&gt;

&lt;p&gt;Python is a popular language amongst beginners, academics and data scientists, and its popularity is steadily growing. In 2024, Python became the &lt;a href="https://github.blog/news-insights/octoverse/octoverse-2024/" rel="noopener noreferrer"&gt;most used language on GitHub&lt;/a&gt;, surpassing JavaScript for the first time. With the number of Python developers steadily increasing, there is more demand than ever to be able to build graphical user interfaces (GUIs) with Python.&lt;/p&gt;

&lt;p&gt;While Python has long been used as a backend language for web development, more and more developers are looking to use it on the frontend too. There are now several frameworks for building full-stack web applications entirely in Python, each with their own approach to solving the issue. Let's take a look at a few of them.&lt;/p&gt;

&lt;p&gt;In this article, we'll compare and contrast five tools for building GUIs in Python:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Streamlit&lt;/li&gt;
&lt;li&gt;Shiny for Python&lt;/li&gt;
&lt;li&gt;NiceGUI&lt;/li&gt;
&lt;li&gt;Reflex&lt;/li&gt;
&lt;li&gt;Anvil&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Don't have the time? Skip the in-depth comparison and go straight to the summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streamlit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="//streamlit.io"&gt;Streamlit&lt;/a&gt; is a popular open-source Python library for building data-driven apps. Streamlit's approach to building web applications is simple and pythonic, which makes it easy to adapt existing Python scripts into Streamlit apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ic6rwqo2fjl0qvkc6tt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1ic6rwqo2fjl0qvkc6tt.png" alt="Screenshot of a Streamlit app showing data on NYC Uber pickups" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Although the Streamlit benefits from being straightforward and user friendly, its simplicity is also its biggest weakness. Any time a user interacts with a Streamlit app, &lt;a href="https://docs.streamlit.io/get-started/fundamentals/main-concepts#data-flow" rel="noopener noreferrer"&gt;the script reruns from top to bottom&lt;/a&gt;. This causes two issues: apps can be slow and state management is tricky. It's also not easy to customize Streamlit apps, which results in most Streamlit apps all looking alike.&lt;/p&gt;

&lt;p&gt;Overall, Streamlit is great for prototyping or building small data-based apps, but it's not the best solution when your app requires more complexity or customization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Easy to turn an existing script into an app&lt;/li&gt;
&lt;li&gt;Great for building dashboards&lt;/li&gt;
&lt;li&gt;Intuitive and straightforward to understand&lt;/li&gt;
&lt;li&gt;Large and active developer community&lt;/li&gt;
&lt;li&gt;Completely free and open-source&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apps can be slow&lt;/li&gt;
&lt;li&gt;Not good for bigger, more complex apps&lt;/li&gt;
&lt;li&gt;Hard to customize&lt;/li&gt;
&lt;li&gt;Minimal features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Shiny for Python
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://shiny.posit.co/py/" rel="noopener noreferrer"&gt;Shiny for Python&lt;/a&gt; is a relatively new library for building interactive web applications in Python. Originally a popular tool for the language R, Shiny has now branched out into Python. Shiny's approach is very &lt;a href="https://shiny.posit.co/py/docs/comp-streamlit.html" rel="noopener noreferrer"&gt;similar to Streamlit's&lt;/a&gt;, but it aims to correct two of Streamlit's biggest flaws: the need to rerun the entire script and the lack of customizability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd9n6fzwp8tlgcywn2um9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd9n6fzwp8tlgcywn2um9.png" alt="A screenshot of a Shiny dashboard" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;
An example Shiny dashboard: &lt;a href="https://gallery.shinyapps.io/template-dashboard-tips1/" rel="noopener noreferrer"&gt;https://gallery.shinyapps.io/template-dashboard-tips1/&lt;/a&gt;



&lt;p&gt;&lt;br&gt;&lt;br&gt;
Instead of reruning the entire script after any user interaction, Shiny only &lt;a href="https://shiny.posit.co/py/docs/overview.html#reactivity" rel="noopener noreferrer"&gt;re-renders elements that have been affected&lt;/a&gt; by that user interaction. This means the entire Shiny script doesn't need to rerun every time a user makes a change, but it leads to less readable and intuitive code. &lt;/p&gt;

&lt;p&gt;Unlike Streamlit, Shiny also allows you to &lt;a href="https://shiny.posit.co/py/docs/ui-customize.html#custom-css" rel="noopener noreferrer"&gt;add CSS classes&lt;/a&gt; to have more control over the look and feel of your app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The script doesn't rerun after every user interaction&lt;/li&gt;
&lt;li&gt;Can use CSS to customize your apps&lt;/li&gt;
&lt;li&gt;Great for building dashboards&lt;/li&gt;
&lt;li&gt;Good documentation and examples&lt;/li&gt;
&lt;li&gt;Completely free and open-source&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Less straightforward code&lt;/li&gt;
&lt;li&gt;Minimal features&lt;/li&gt;
&lt;li&gt;Newer library with a smaller community&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  NiceGUI
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://nicegui.io/" rel="noopener noreferrer"&gt;NiceGUI&lt;/a&gt; is Python library for building web applications that was &lt;a href="https://nicegui.io/#why" rel="noopener noreferrer"&gt;developed explicitly as an alternative to Streamlit&lt;/a&gt;. Like Shiny for Python, NiceGUI doesn't rerun the entire script after every user interaction. It also allows you to add CSS classes, including Tailwind classes, to your code for &lt;a href="https://nicegui.io/documentation/section_styling_appearance" rel="noopener noreferrer"&gt;more customization of the look and feel&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqah4vta8rz9ybh9t6j33.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqah4vta8rz9ybh9t6j33.png" alt="Screenshot of NiceGUI's website" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;
NiceGUI's website was built using NiceGUI.



&lt;p&gt;&lt;br&gt;&lt;br&gt;
NiceGUI code lacks the simplicity and intuitiveness of Streamlit code, and its approach to reactivity and state management is more opaque than Shiny's. There are different methods and decorators for different types of UI updating, so it can be difficult to know how to get the UI to update. In addition, NiceGUI's documentation is less robust than Streamlit's or Shiny's, and there are no tutorials or live examples. This makes getting started with NiceGUI more of a challenge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The script doesn't rerun after every user interaction&lt;/li&gt;
&lt;li&gt;Can use inline CSS, stylesheets and Tailwind to customize your apps&lt;/li&gt;
&lt;li&gt;Completely free and open-source&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Incomplete documentation&lt;/li&gt;
&lt;li&gt;No official tutorials&lt;/li&gt;
&lt;li&gt;Not great for building dashboards&lt;/li&gt;
&lt;li&gt;Updating the UI is not always straightforward or consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reflex
&lt;/h2&gt;

&lt;p&gt;While &lt;a href="https://reflex.dev/" rel="noopener noreferrer"&gt;Reflex&lt;/a&gt; is also a Python library for building interactive GUIs, its approach is different than Streamlit, Shiny and NiceGUI. Unlike the previous options, Reflex doesn't abstract away much of traditional web development. This means that if you are not familiar with traditional web development, Reflex comes with a bit of a learning curve. However, it also means that Reflex uses less magic to deal with reactivity and state management.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvgovyyusabojllulvgdm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvgovyyusabojllulvgdm.png" alt="Screenshot of a Reflex app" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;
An example Reflex app: &lt;a href="https://dashboard-new.reflex.run/" rel="noopener noreferrer"&gt;https://dashboard-new.reflex.run&lt;/a&gt; 



&lt;p&gt;&lt;br&gt;&lt;br&gt;
Reflex uses a &lt;a href="https://reflex.dev/docs/state/overview/" rel="noopener noreferrer"&gt;State class&lt;/a&gt; to keep track of the state of the app, and methods within the State class update the app's state. Although Reflex code is more verbose than the previous examples, it is more explicit about state management and separating front-end and back-end code. This means there is less "magic" involved in Reflex apps so they are easier to adapt and extend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Explicit state management&lt;/li&gt;
&lt;li&gt;Robust documentation with lots of examples&lt;/li&gt;
&lt;li&gt;Templates to get started&lt;/li&gt;
&lt;li&gt;Has an AI app generator&lt;/li&gt;
&lt;li&gt;Customizable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Need to learn web development &lt;/li&gt;
&lt;li&gt;Code is not that simple and straightforward&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anvil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt; takes a very different approach to building Python web applications. While the previous options are Python packages built as alternatives to Streamlit, Anvil was built to be &lt;a href="https://en.wikipedia.org/wiki/Visual_Basic_(classic)" rel="noopener noreferrer"&gt;Visual Basic&lt;/a&gt; for the web.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqumqv950p86j8mbbqlh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqumqv950p86j8mbbqlh.png" alt="Screenshot of an Anvil application" width="800" height="484"&gt;&lt;/a&gt; &lt;/p&gt;
An example Anvil app. Learn more: &lt;a href="https://anvil.works/learn/examples/task-manager-app" rel="noopener noreferrer"&gt;https://anvil.works/learn/examples/task-manager-app&lt;/a&gt;





&lt;p&gt;Anvil is a cloud-based IDE where you visually build your user interfaces and write Python code for the front-end and back-end. Like Reflex, Anvil &lt;a href="https://anvil.works/articles/client-vs-server" rel="noopener noreferrer"&gt;doesn't abstract away web development&lt;/a&gt;, but it aims to make it more approachable for non-web developers. To build an Anvil app, you &lt;a href="https://anvil.works/docs/ui/quickstart" rel="noopener noreferrer"&gt;drag and drop components onto the page&lt;/a&gt; to build your UI (you can also create components in code), then write client-side code to make the components interactive and server-side code for back-end functions. To call server functions from your UI, you just use &lt;code&gt;anvil.server.call&lt;/code&gt;. Client-side code runs entirely in the browser which makes it super fast.&lt;/p&gt;

&lt;p&gt;Anvil allows you to do everything in Python, but doesn't try to stop you &lt;a href="https://anvil.works/blog/escape-hatches-and-ejector-seats" rel="noopener noreferrer"&gt;if you want to break out&lt;/a&gt;. You can still use HTML, CSS and JS in your Anvil apps. What's more, Anvil includes a whole host of features that none of the other options having including a &lt;a href="https://anvil.works/docs/data-tables" rel="noopener noreferrer"&gt;built-in database&lt;/a&gt;, &lt;a href="https://anvil.works/docs/users" rel="noopener noreferrer"&gt;out-of-the-box user management&lt;/a&gt; and &lt;a href="https://anvil.works/docs/deployment/quickstart" rel="noopener noreferrer"&gt;instant cloud hosting&lt;/a&gt;. &lt;/p&gt;

&lt;h3&gt;
  
  
  Pros
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Visually build your User Interface&lt;/li&gt;
&lt;li&gt;Lots of built-in features and integrations&lt;/li&gt;
&lt;li&gt;Instant unlimited cloud hosting&lt;/li&gt;
&lt;li&gt;Explicit state management and separation of client and server&lt;/li&gt;
&lt;li&gt;In-depth documentation and tutorials&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Need to get used to a new IDE&lt;/li&gt;
&lt;li&gt;Client-server architecture may be unfamiliar to non-web developers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://streamlit.io/" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Quick to learn, straightforward code, easy for simple dashboards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weakness&lt;/strong&gt;: The entire script re-runs with user interaction, limited in features and flexibility&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://shiny.posit.co/py/" rel="noopener noreferrer"&gt;Shiny for Python&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Only re-runs relevant functions, great for building dashboards, good documentation and examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Newer library with a smaller community, minimal features&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://nicegui.io/" rel="noopener noreferrer"&gt;NiceGUI&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Doesn't re-run the script after each interaction, can use CSS and Tailwind for customixation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Code isn't always straightforward, not great for dashboarding, incomplete documentation with no official tutorials&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://reflex.dev/" rel="noopener noreferrer"&gt;Reflex&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Uses explicit state management, very customizable, robust documentation and examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Larger learning curve, need to understand web development&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://anvil.works/" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Out-of-the-box instant cloud hosting , drag-and-drop UI builder, more features for complex apps, in-depth documentation with lots of tutorials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Unfamiliar IDE, client-server architecture may be unfamiliar as well&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;With Python being the most popular programming language, there are more tools than ever for building interactive web applications entirely in Python.&lt;/p&gt;

&lt;p&gt;&lt;a href="//streamlit.io"&gt;Streamlit&lt;/a&gt; is one such tool that offers a simple, Pythonic approach. However, it suffers from two main weaknesses: the script needs to be re-run after every user interaction, and customizing the look and feel of a Streamlit app is dificult. &lt;a href="https://shiny.posit.co/py/" rel="noopener noreferrer"&gt;Shiny for Python&lt;/a&gt; is a great alternative for building dashboards and small data-backed apps. Shiny only re-renders code as necessary - not the entire script, and offers more flexibility in styling your app. &lt;a href="https://nicegui.io/" rel="noopener noreferrer"&gt;NiceGUI&lt;/a&gt; offers a similar approach, but with less intuitive code and no tutorials or live examples, is more of a challenge to get started with. &lt;a href="https://reflex.dev/" rel="noopener noreferrer"&gt;Reflex&lt;/a&gt; is a great option if you want more flexibility in building your app's UI, but you'll need to learn a bit of traditional web development.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/"&gt;Anvil&lt;/a&gt; takes a different approach to building Python web apps. With Anvil, you &lt;a href="https://anvil.works/docs/ui/quickstart" rel="noopener noreferrer"&gt;drag and drop components&lt;/a&gt; to build your user interface then write Python code to make it interactive. Anvil also comes with many out-of-the-box features such as a &lt;a href="https://anvil.works/docs/data-tables" rel="noopener noreferrer"&gt;built-in database&lt;/a&gt;, &lt;a href="https://anvil.works/docs/users" rel="noopener noreferrer"&gt;user management&lt;/a&gt; and &lt;a href="https://anvil.works/docs/deployment/quickstart" rel="noopener noreferrer"&gt;instant cloud hosting&lt;/a&gt;. If you want to build more complex apps and host them easily for free, then Anvil is your best bet.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python Admin Dashboard Template</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Tue, 29 Apr 2025 09:48:31 +0000</pubDate>
      <link>https://dev.to/anvil/python-admin-dashboard-template-3f4o</link>
      <guid>https://dev.to/anvil/python-admin-dashboard-template-3f4o</guid>
      <description>&lt;p&gt;Anvil is a powerful and versatile tool for building full-stack web applications. With Anvil, you can &lt;a href="https://anvil.works/blog/build-a-web-ui-with-python?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;quickly and easily create your UI&lt;/a&gt; and write Python code to control the front-end and back-end of your app. Creating apps with Anvil is fast and flexible, making it well-suited for building dashboards, SaaS products, and internal tools.&lt;/p&gt;

&lt;p&gt;Whatever your use case, Anvil can help you build it. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://admin-dashboard-sampler.anvil.app" rel="noopener noreferrer"&gt;Explore the dashboard&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpijemkknm6z8yg7jcen9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpijemkknm6z8yg7jcen9.png" alt="Screenshot of the profile page" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ready to dive into the source code?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard#clone:OONBCUWWWA7LRQDM=2MBSGCHCL4MIG6WZJMFGX7OW"&gt;Clone the app&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create powerful apps with a powerful tool
&lt;/h2&gt;

&lt;p&gt;With a &lt;a href="https://anvil.works/docs/ui/quickstart?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;drag-and-drop UI builder&lt;/a&gt;, &lt;a href="https://anvil.works/learn/tutorials/database-backed-apps?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;integrated database&lt;/a&gt; and &lt;a href="https://anvil.works/learn/tutorials/multi-user-apps?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;built-in user management&lt;/a&gt;, Anvil takes care of the hard stuff so you can focus on building your app. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/docs/email"&gt;Send emails&lt;/a&gt;, &lt;a href="https://anvil.works/docs/working-with-files/creating-pdf-files?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;generate PDFs&lt;/a&gt; and &lt;a href="https://anvil.works/docs/deployment/quickstart?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;deploy your app to the web&lt;/a&gt; in just a click.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7tyaqs4duc17u1lxm1q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk7tyaqs4duc17u1lxm1q.png" alt="Screenshot of the analytics page" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build your UI with Material 3
&lt;/h2&gt;

&lt;p&gt;The Admin Dashboard uses the &lt;a href="https://m3.material.io/" rel="noopener noreferrer"&gt;Material 3&lt;/a&gt; design system for a sleek and modern design. Build your own dashboards by dragging and dropping Material 3 components onto the page. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc9q96tvc3cojtjuoox1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffc9q96tvc3cojtjuoox1.png" alt="Screenshot of the settings page" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it for yourself
&lt;/h2&gt;

&lt;p&gt;Clone the Admin Dashboard Template to explore it for yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard#clone:OONBCUWWWA7LRQDM=2MBSGCHCL4MIG6WZJMFGX7OW"&gt;Clone the app&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/learn/examples/admin-dashboard"&gt;Sign up for Anvil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
    </item>
    <item>
      <title>Connect your Anvil apps to GitHub</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Wed, 30 Aug 2023 15:00:56 +0000</pubDate>
      <link>https://dev.to/anvil/connect-your-anvil-apps-to-github-5dm</link>
      <guid>https://dev.to/anvil/connect-your-anvil-apps-to-github-5dm</guid>
      <description>&lt;h1&gt;
  
  
  Version control, collaboration and open-source made easy
&lt;/h1&gt;

&lt;p&gt;Anvil's new &lt;a href="https://anvil.works/docs/version-control-new-ide/git?utm_source=crosspost:dev.to:/blog/github-integration"&gt;GitHub integration&lt;/a&gt; is here to make development and collaboration more efficient than ever by allowing you to keep your Anvil app's source code in a &lt;a href="https://github.com/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repository. Whether you're collaborating with teammates or on open-source projects, you can store your source code on GitHub, make pull requests and use all of GitHub's version control and collaboration tools. You don't need to be a Git expert to collaborate using GitHub: Anvil takes care of keeping your app and GitHub repository in sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/docs/version-control-new-ide/git" rel="noopener noreferrer"&gt;Learn more in our documentation.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Streamlined development and collaboration
&lt;/h2&gt;

&lt;p&gt;The Anvil GitHub integration fits neatly into your workflow. On Anvil's Business Plan and above, you can create private repositories to store and manage Anvil code with the rest of your company's codebase. Open issues, create pull requests, review changes and manage conflicts within GitHub. Anvil's built-in version control tab lets you manage your repository and keep track of code changes from within your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd11xd31j9umpal6qv2s4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd11xd31j9umpal6qv2s4.png" alt="The version control tab in the Anvil Editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Create and contribute to open-source projects
&lt;/h2&gt;

&lt;p&gt;All Anvil users can connect to public repositories for free, so you can create and contribute to open-source projects. Publish your Anvil code on GitHub and share it with other Anvil developers, or open pull requests in other repositories to contribute to Anvil community projects. Show off your Anvil code on your GitHub profile and build a portfolio of your Anvil projects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbawyhbp9rdilha2p4cy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flbawyhbp9rdilha2p4cy.png" alt="The Anvil Extras open-source project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;To get started with the GitHub integration, you'll need to either save an app to a new GitHub repository or clone an existing repository into Anvil. No need to be a pro at Git to get started: all your changes will automatically be pushed to the repository, and other developers’ changes will show up live in your app.&lt;/p&gt;

&lt;p&gt;Want to use Git but don't want to use GitHub? No problem. &lt;a href="https://anvil.works/docs/version-control-new-ide/git#working-with-other-git-remotes?utm_source=crosspost:dev.to:/blog/github-integration"&gt;Anvil supports other Git remotes as well&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ready to get started? Check out our GitHub integration &lt;a href="https://anvil.works/docs/version-control-new-ide/git/quickstart?utm_source=crosspost:dev.to:/blog/github-integration"&gt;quickstart tutorial&lt;/a&gt; or dive right in and &lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/github-integration"&gt;start building an app&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/blog/github-integration"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/github-integration"&gt;Sign up for Anvil&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>github</category>
    </item>
    <item>
      <title>Integrate Mapbox into your Web App using only Python</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Wed, 24 May 2023 15:35:26 +0000</pubDate>
      <link>https://dev.to/anvil/integrate-mapbox-into-your-web-app-using-only-python-2jhb</link>
      <guid>https://dev.to/anvil/integrate-mapbox-into-your-web-app-using-only-python-2jhb</guid>
      <description>&lt;h1&gt;
  
  
  Add powerful Mapbox maps into your Anvil app
&lt;/h1&gt;

&lt;blockquote&gt;
&lt;p&gt;Watch me &lt;a href="https://www.youtube.com/watch?v=A0QSCoQ6ozs&amp;amp;t=1072s"&gt;live code this app&lt;/a&gt; during a Mapbox webinar.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://mapbox.com"&gt;Mapbox&lt;/a&gt; is a powerful tool for building interactive customizable maps and integrating location and navigation data into your apps and websites. With Anvil, you can add Mapbox to your web applications entirely in Python - no JavaScript needed.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/articles/mapbox-isochrone"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web applications with nothing but Python. In this tutorial, I'm going to show you how to build an Anvil app, which uses Mapbox's &lt;a href="https://docs.mapbox.com/api/navigation/isochrone/"&gt;Isochrone API&lt;/a&gt; to estimate how far you can travel given a starting point and a certain amount of time. My app build is based on &lt;a href="https://docs.mapbox.com/help/tutorials/get-started-isochrone-api/"&gt;this tutorial&lt;/a&gt;, but requires no JS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Iy54Ko5D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://anvil.works/articles/img/mapbox-isochrone/mapbox-app.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Iy54Ko5D--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://anvil.works/articles/img/mapbox-isochrone/mapbox-app.png" alt="Screenshot of the final app" width="800" height="405"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;First, we'll set up our app by creating a blank Anvil app, getting a Mapbox access key and adding a &lt;code&gt;Map&lt;/code&gt; object to our new app. Next, we'll add a geocoder to the app so that users can search for a location, and then we'll use the Isochrone API estimate how far we can travel in a certain amount of time. Finally, we'll build the UI for our app.&lt;/p&gt;

&lt;p&gt;Let's get started!  &lt;/p&gt;




&lt;h2&gt;
  
  
  1. Set up the App and Mapbox Map
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Create an Anvil app
&lt;/h3&gt;

&lt;p&gt;We first need to create a blank Anvil app: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--afa0SACO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yz4kbcifnocgir5h4eh4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--afa0SACO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yz4kbcifnocgir5h4eh4.png" alt="Location of the Create Blank App button" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Get a Mapbox access key
&lt;/h3&gt;

&lt;p&gt;Next, we need to create a Mapbox account and get an access key. You can sign up for a free account at &lt;a href="https://mapbox.com"&gt;mapbox.com&lt;/a&gt;, which will give you a public access token. &lt;/p&gt;

&lt;p&gt;Now that we have a Mapbox account and an access token, we can add a basic map to our Anvil app. This step is based on Mapbox's &lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/"&gt;quickstart tutorial&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;We first need to add the Mapbox GL JS library and CSS file into our Anvil app. Add the following code to the &lt;a href="https://anvil.works/docs/client/javascript#using-native-javascript-libraries?utm_source=crosspost:dev.to:/articles/mapbox-isochrone"&gt;Native Libraries&lt;/a&gt; section of your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/v2.1.1/mapbox-gl.css'&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;'stylesheet'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blank Anvil app we created already has a Form (&lt;code&gt;Form1&lt;/code&gt;) that will load when someone visits our app. In our &lt;code&gt;Form1&lt;/code&gt; code, we need to add the following import statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;anvil.js.window&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mapboxgl&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;anvil.js&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;anvil.http&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://anvil.works/docs/client/javascript/accessing-javascript?utm_source=crosspost:dev.to:/articles/mapbox-isochrone"&gt;&lt;code&gt;anvil.js.window&lt;/code&gt;&lt;/a&gt; allows us to access JS libraries in our Anvil app and interact with JS objects from Python. This way, we can still make use of Mapbox's powerful JS library, but we can do it with Python.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Map object
&lt;/h3&gt;

&lt;p&gt;We need to add a placeholder component to our app that will hold our map. A &lt;code&gt;Spacer&lt;/code&gt; works nicely because we can easily set the height. Drag and drop one from the Toolbox onto the form, and set its height to something reasonable.&lt;/p&gt;

&lt;p&gt;We want the map to be displayed when the form is opened, so select the form and scroll down to the bottom of the Properties panel to create an event handler for the &lt;code&gt;show&lt;/code&gt; event:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--6I6tTN34--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://anvil-website-static.s3.eu-west-2.amazonaws.com/blog/mapbox-isochrone/spacer-and-form-show.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--6I6tTN34--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://anvil-website-static.s3.eu-west-2.amazonaws.com/blog/mapbox-isochrone/spacer-and-form-show.gif" alt="Adding a Spacer component and the  raw `form_show` endraw  event" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anvil has created the &lt;code&gt;form_show&lt;/code&gt; event handler for you, and this is where we want to write code to display our map. &lt;/p&gt;

&lt;p&gt;In &lt;code&gt;form_show&lt;/code&gt;, we first need to set our Mapbox access token then create a &lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/map/"&gt;Mapbox &lt;code&gt;Map&lt;/code&gt; object&lt;/a&gt;. We'll pass in a dictionary that tells the &lt;code&gt;Map&lt;/code&gt; object where it should be displayed (the &lt;code&gt;Spacer&lt;/code&gt; component we added), how the map should be styled, where to center the map (as &lt;code&gt;[longitude, latitude]&lt;/code&gt; coordinates) and how zoomed in the map should be.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""This method is called when the HTML panel is shown on the screen"""&lt;/span&gt;
    &lt;span class="c1"&gt;#I defined my access token in the __init__
&lt;/span&gt;    &lt;span class="n"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; 

    &lt;span class="c1"&gt;#put the map in the spacer 
&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'container'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_dom_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spacer_1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                                &lt;span class="s"&gt;'style'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'mapbox://styles/mapbox/streets-v11'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#use the standard Mapbox style
&lt;/span&gt;                                &lt;span class="s"&gt;'center'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.1218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;52.2053&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;#center on Cambridge
&lt;/span&gt;                                &lt;span class="s"&gt;'zoom'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Why are we using the Show event rather than setting this up in the constructor?&lt;/strong&gt;&lt;br&gt;
  We need our spacer component to be on the page before we initialise Mapbox's API. &lt;code&gt;__init__&lt;/code&gt; is called before adding the Form to the page, but the show event is triggered afterwards. (&lt;a href="https://anvil.works/docs/client/components#the-show-and-hide-events?utm_source=crosspost:dev.to:/articles/mapbox-isochrone"&gt;See more in the Anvil docs&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Add a Marker object
&lt;/h3&gt;

&lt;p&gt;Finally, we can add a &lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/markers/#marker"&gt;&lt;code&gt;Marker&lt;/code&gt; object&lt;/a&gt; to our map. We first need to create the object, give it a color and set &lt;code&gt;draggable&lt;/code&gt; to &lt;code&gt;True&lt;/code&gt;. Then we can set its &lt;code&gt;[longitude, latitude]&lt;/code&gt; coordinates and add it to the map. This is added to the &lt;code&gt;form_show&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Marker&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'color'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'#5a3fc0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'draggable'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLngLat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.1218&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;52.2053&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sgcitiul--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://anvil.works/articles/img/mapbox-isochrone/map-with-marker.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sgcitiul--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://anvil.works/articles/img/mapbox-isochrone/map-with-marker.png" alt="Mapbox map with a Marker added" width="800" height="480"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Add a Geocoder
&lt;/h2&gt;

&lt;p&gt;Mapbox's &lt;a href="https://docs.mapbox.com/api/search/geocoding/"&gt;Geocoding API&lt;/a&gt; allows us to easily turn location searches into longitude and latitude coordinates. We can use the &lt;a href="https://docs.mapbox.com/mapbox-gl-js/plugins/#mapbox-gl-geocoder"&gt;Mapbox GL JS Geocoder plugin&lt;/a&gt; to add a search bar to our map. When a users searches for and selects a place, the map will fly over to this new location. &lt;/p&gt;

&lt;h3&gt;
  
  
  Import the geocoder
&lt;/h3&gt;

&lt;p&gt;We first need to add the Mapbox Geocoder. In Native Libraries, add the following lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.2.0/mapbox-gl-geocoder.min.js'&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;'stylesheet'&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.2.0/mapbox-gl-geocoder.css'&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;'text/css'&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, change the import statement you added in the previous step to also import &lt;code&gt;MapboxGeocoder&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;anvil.js.window&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MapboxGeocoder&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Add a geocoder to the map
&lt;/h3&gt;

&lt;p&gt;Next we need to instantiate a &lt;a href="https://github.com/mapbox/mapbox-gl-geocoder/blob/master/API.md"&gt;&lt;code&gt;MapboxGeocoder&lt;/code&gt; object&lt;/a&gt; by passing in our access token. Then we can add it to our map. The following code should be added to the &lt;code&gt;form_show&lt;/code&gt; event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geocoder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MapboxGeocoder&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'accessToken'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mapboxgl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="s"&gt;'marker'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;#we've already added a marker
&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addControl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geocoder&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Move the marker
&lt;/h3&gt;

&lt;p&gt;We now have a geocoder added to our map. It will center the map to the location we search for, but it doesn't move the marker. Let's change that. We'll write a function that gets the new coordinates from the geocoder and sets the marker to these coordinates after the user selects a location from the geocoder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="p"&gt;...&lt;/span&gt;

    &lt;span class="c1"&gt;#when the 'result' event is triggered, call self.move_marker
&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geocoder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'result'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_marker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;move_marker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#get the [longitude, latitude] coordinates from the JS object returned from 'result'
&lt;/span&gt;    &lt;span class="n"&gt;lnglat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'result'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'geometry'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'coordinates'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLngLat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lnglat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--9kS0mZls--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://anvil-website-static.s3.eu-west-2.amazonaws.com/blog/mapbox-isochrone/geocoder-search.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9kS0mZls--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://anvil-website-static.s3.eu-west-2.amazonaws.com/blog/mapbox-isochrone/geocoder-search.gif" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Add the Isochrone API
&lt;/h2&gt;

&lt;p&gt;Now that we have a map, a marker and a geocoder, we can calculate the isochrone lines that tell us how far we can travel given a specified amount of time.&lt;/p&gt;

&lt;p&gt;To make the request to the Isochrone API, we need to pass in the &lt;code&gt;profile&lt;/code&gt; ('walking', 'cycling' or 'driving'), the coordinates of our starting point (as &lt;code&gt;[longitude, latitude]&lt;/code&gt;) and the number of minutes we want to calculate the isochrone for (&lt;code&gt;contours_minutes&lt;/code&gt;). We can get the longitude and latitude coordinates from our marker and collect the &lt;code&gt;profile&lt;/code&gt; and minutes from the front-end of our app (we'll build this in the next step).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_iso&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contours_minutes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lnglat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLngLat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"https://api.mapbox.com/isochrone/v1/mapbox/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lnglat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lnglat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?contours_minutes=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;contours_minutes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;polygons=true&amp;amp;access_token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;response&lt;/code&gt; will be a JSON object representing the isochrone lines we want to add to our map. In order to add these, we first need to add a &lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/"&gt;Source&lt;/a&gt;, which tells the map which data it should display, and a &lt;a href="https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/"&gt;Layer&lt;/a&gt;, which defines how to style the source data.&lt;/p&gt;

&lt;p&gt;We should set the &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;layer&lt;/code&gt; at the beginning of our &lt;code&gt;get_iso&lt;/code&gt; function. Then after we get the response from the API, we can set that as the data for the source to display:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_iso&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contours_minutes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#we only want to create the source and layer once
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'iso'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'geojson'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                        &lt;span class="s"&gt;'data'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'FeatureCollection'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                        &lt;span class="s"&gt;'features'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[]}})&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addLayer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'isoLayer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;'type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'fill'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;'source'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'iso'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;#the id of the source we just defined
&lt;/span&gt;                                &lt;span class="s"&gt;'layout'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
                                &lt;span class="s"&gt;'paint'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                                &lt;span class="s"&gt;'fill-color'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'#5a3fc0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                &lt;span class="s"&gt;'fill-opacity'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;

    &lt;span class="n"&gt;lnglat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLngLat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"https://api.mapbox.com/isochrone/v1/mapbox/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lnglat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lng&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lnglat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;?contours_minutes=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;contours_minutes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;amp;polygons=true&amp;amp;access_token=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mapbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getSource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'iso'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;setData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's now build a front-end for our app so that users can specify an amount of time and a mode of travel.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Build the Front End
&lt;/h2&gt;

&lt;p&gt;We can add two DropDown menus to our app so that users can select an amount of time and a mode of travel. Name the first DropDown &lt;code&gt;time_dropdown&lt;/code&gt; and the other &lt;code&gt;profile_dropdown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--SYPpJBCj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://anvil-website-static.s3.eu-west-2.amazonaws.com/blog/mapbox-isochrone/add-dropdowns.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--SYPpJBCj--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://anvil-website-static.s3.eu-west-2.amazonaws.com/blog/mapbox-isochrone/add-dropdowns.gif" alt="Adding DropDown menus to the form" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When we pass an amount of time as the &lt;code&gt;contours_minutes&lt;/code&gt; argument to our &lt;code&gt;get_iso&lt;/code&gt; function, we need it to be a number as a string (e.g. '10'). If we set &lt;code&gt;time_dropdown.items&lt;/code&gt; from code, we can use a list of 2-tuples where the the first element of each tuple will be displayed in the DropDown and the second element will be the &lt;code&gt;selected_value&lt;/code&gt; of the DropDown:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Set Form properties and Data Bindings.    
&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="s"&gt;'10 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'10'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'20 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'20'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'30 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'30'&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The items for the &lt;code&gt;profile_dropdown&lt;/code&gt; should be 'walking', 'cycling' and 'driving'. These can be set directly in the Properties panel of the Editor or via code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'walking'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'cycling'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'driving'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We now need to add event handlers so that &lt;code&gt;get_iso&lt;/code&gt; is called whenever the value of a DropDown menu changes. For each DropDown on your form, scroll down to the bottom of the Properties panel to create an event handler for the &lt;code&gt;change&lt;/code&gt; event. Then call &lt;code&gt;get_iso&lt;/code&gt; with the selected values of the DropDowns as arguments to the function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;time_dropdown_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="s"&gt;"""This method is called when an item is selected"""&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_iso&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;profile_dropdown_change&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="s"&gt;"""This method is called when an item is selected"""&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_iso&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we can also call &lt;code&gt;get_iso&lt;/code&gt; when the Marker is dragged. We'll do this just like we did in Step 2 when we caught the &lt;code&gt;result&lt;/code&gt; event being triggered on the geocoder and moved the Marker. This time, we need to catch when the &lt;code&gt;drag&lt;/code&gt; event is triggered on the Marker, then call &lt;code&gt;get_iso&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;

      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'drag'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;marker_dragged&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;marker_dragged&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;drag&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_iso&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;profile_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time_dropdown&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;selected_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! We now have a fully working app built from the Mapbox Isochrone API that shows us how far we can travel in a specific amount of time. I made some changes to the style of my map and app, which I'll show you how to do in the next (optional) step.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--YkpTRcgY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5e2alv1ybabsfzgsr2v7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--YkpTRcgY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5e2alv1ybabsfzgsr2v7.gif" alt="The finished app in action" width="" height=""&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check out the full source code of the app &lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/articles/mapbox-isochrone#clone:HYM75SW2AAJQ74SJ%3dXVRPOQL2FURO7SEWXB5B6SES"&gt;here&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Optional: Style your app (Advanced)
&lt;/h2&gt;

&lt;p&gt;To make my app look a bit nicer, I first changed the style of the Mapbox map. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.mapbox.com/mapbox-gl-js/api/map/"&gt;This page&lt;/a&gt; lists Mapbox's predefined styles, but you can also design your own in the &lt;a href="https://www.mapbox.com/mapbox-studio"&gt;Studio&lt;/a&gt; or choose from the &lt;a href="https://www.mapbox.com/gallery/"&gt;gallery&lt;/a&gt;. I chose the style &lt;a href="https://www.mapbox.com/gallery/#frank"&gt;Frank&lt;/a&gt; from the gallery and added this to my account. After adding the style, you'll be in Mapbox Studio. Click "Share" in the top menu and copy the 'Style URL'. When instantiating your map, pass this URL as the map's 'style'. &lt;/p&gt;

&lt;p&gt;I also changed the color of the Marker and the Isochrone fill to better match the 'Frank' style. I also set the height of the Spacer holding the map to &lt;code&gt;100vh&lt;/code&gt; so that the map would fill the entire page. &lt;/p&gt;

&lt;p&gt;I then edited the app's HTML to remove the app bar and add an extra &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element that floats on top of the map. At the top of &lt;code&gt;standard-page.html&lt;/code&gt;, I commented out the &lt;code&gt;app-bar&lt;/code&gt; and added a &lt;code&gt;floating-menu&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt; &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"structure"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!--   &amp;lt;div class="app-bar" anvil-drop-container=".anvil-container" anvil-drop-redirect=".placeholder"&amp;gt;
    &amp;lt;a class="sidebar-toggle" anvil-if-slot-empty="top-left-btn" anvil-hide-if-slot-empty="left-nav" anvil-drop-slot="top-left-btn" href="javascript:void(0)"&amp;gt;&amp;lt;i class="fa fa-bars"&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;a class="sidebar-toggle anvil-designer-only" anvil-if-slot-empty="top-left-btn" anvil-if-slot-empty="left-nav" anvil-drop-slot="top-left-btn"&amp;gt;&amp;lt;i class="fa fa-blank"&amp;gt;&amp;lt;/i&amp;gt;&amp;lt;/a&amp;gt;
    &amp;lt;div class="top-left-btn" anvil-slot="top-left-btn"&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class="title" anvil-slot="title"&amp;gt;
      &amp;lt;div class="placeholder anvil-designer-only" anvil-if-slot-empty="title" anvil-drop-here&amp;gt;Drop title here&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class="app-bar-nav" anvil-slot="nav-right"&amp;gt;
      &amp;lt;div class="placeholder anvil-designer-only" anvil-if-slot-empty="nav-right" anvil-drop-here&amp;gt;Drop a FlowPanel here&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div style="clear:both"&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/div&amp;gt; --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"floating-menu"&lt;/span&gt; &lt;span class="na"&gt;anvil-drop-here&lt;/span&gt; &lt;span class="na"&gt;anvil-slot=&lt;/span&gt;&lt;span class="s"&gt;".floating-menu"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added my DropDown menus to this floating element and edited its class in &lt;code&gt;theme.css&lt;/code&gt; so that it's background is transparent, and it stays fixed to the map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.floating-menu&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;absolute&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;z-index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;250px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;min-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50px&lt;/span&gt; &lt;span class="m"&gt;100px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30px&lt;/span&gt; &lt;span class="m"&gt;40px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;229&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;213&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;194&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/articles/mapbox-isochrone"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The PiCast: Connect a Raspberry Pi Pico W to an Anvil app</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Fri, 12 May 2023 14:22:08 +0000</pubDate>
      <link>https://dev.to/anvil/the-picast-connect-a-raspberry-pi-pico-w-to-an-anvil-app-453a</link>
      <guid>https://dev.to/anvil/the-picast-connect-a-raspberry-pi-pico-w-to-an-anvil-app-453a</guid>
      <description>&lt;h1&gt;
  
  
  Connect a Pico W to an Anvil app
&lt;/h1&gt;

&lt;p&gt;The Pico W is Raspberry Pi's powerful WiFi-enabled microcontroller board. Using the &lt;a href="https://anvil.works/docs/uplink/pico?utm_source=crosspost:dev.to:/blog/picast-video"&gt;Anvil Uplink&lt;/a&gt;, you can connect your Pico W to an Anvil app. Meredydd sat down with the lovely people from &lt;a href="https://www.tomshardware.com/"&gt;Tom's Hardware&lt;/a&gt; to demonstrate how to do just that. &lt;/p&gt;

&lt;p&gt;In his episode of &lt;a href="https://www.tomshardware.com/news/introducing-pi-cast"&gt;The PiCast&lt;/a&gt;, Meredydd builds two projects, live on stream:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Displaying a message on an e-ink display&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Logging data from a sensor&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Watch the episode to learn how it's done:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/y7AQiLbLyck"&gt;
&lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Build your own app with Anvil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/blog/picast-video"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/picast-video"&gt;Sign up for Anvil&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Want to build an app of your own? Get started with one of our &lt;a href="https://anvil.works/learn/tutorials?utm_source=crosspost:dev.to:/blog/picast-video"&gt;tutorials&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>raspberrypi</category>
      <category>python</category>
    </item>
    <item>
      <title>Directly upload large files to the cloud with Anvil</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Thu, 30 Mar 2023 16:16:55 +0000</pubDate>
      <link>https://dev.to/anvil/directly-upload-large-files-to-the-cloud-with-anvil-2529</link>
      <guid>https://dev.to/anvil/directly-upload-large-files-to-the-cloud-with-anvil-2529</guid>
      <description>&lt;p&gt;You might need to build a web app that allows users to upload very large files. If you were building an &lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;Anvil&lt;/a&gt; app, you would normally use a &lt;a href="https://anvil.works/docs/client/components/basic#fileloader?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;FileLoader&lt;/a&gt; component to capture the user's file then store the file in a &lt;a href="https://anvil.works/docs/tables?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;Data Table&lt;/a&gt;. But if your file is many gigabytes in size, you might see timeouts while uploading or exceed the storage limits of your Data Tables. Instead, it makes sense to store giant files directly in a cloud storage service, such as Amazon S3. &lt;/p&gt;

&lt;p&gt;Fortunately, Anvil makes it easy to use third-party cloud services on the back end, and Javascript libraries on the front end. In this example, we'll walk through how to use the use the &lt;a href="https://uppy.io/" rel="noopener noreferrer"&gt;&lt;code&gt;uppy.io&lt;/code&gt; JavaScript widget&lt;/a&gt; and &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html" rel="noopener noreferrer"&gt;Amazon &lt;code&gt;boto3&lt;/code&gt; library&lt;/a&gt; to upload files from Anvil directly to an S3 bucket.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;New to Anvil? Anvil is a platform for building full-stack web applications with nothing but Python. This is a fairly advanced how-to guide -- you might want to &lt;a href="https://anvil.works/learn/tutorials/feedback-form?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;start with a quick tutorial&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Check out the app we're going to build by &lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/direct-s3-upload#clone:HEQEC5XZDJO3DONU%3dIT2JKSLWJOXJKFPX3SMLUBCI"&gt;cloning the source code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We'll build the app in five steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, we'll create a new Anvil app.&lt;/li&gt;
&lt;li&gt;We'll then set up an Amazon S3 bucket with the proper permissions.&lt;/li&gt;
&lt;li&gt;Next, we'll add the Uppy widget to our app.&lt;/li&gt;
&lt;li&gt;We then need to get a presigned URL using Amazon's &lt;code&gt;boto3&lt;/code&gt; library.&lt;/li&gt;
&lt;li&gt;Finally, we'll provide the presigned URL to Uppy which will upload the file to S3.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Step 1: Create an Anvil app
&lt;/h2&gt;

&lt;p&gt;First, log into &lt;a href="https://anvil.works/login?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;Anvil&lt;/a&gt; and create a new blank app. We'll need to make use of our app's &lt;a href="https://anvil.works/docs/security/encrypting-secret-data" rel="noopener noreferrer"&gt;App Secrets&lt;/a&gt; to store the AWS access keys that we'll acquire in the next step, so ahead and add the App Secrets service to your app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil.works%2Fblog%2Fimg%2Fupload-large-files-to-s3%2Fadd-app-secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil.works%2Fblog%2Fimg%2Fupload-large-files-to-s3%2Fadd-app-secrets.png" alt="Adding the App Secrets service in the Anvil Editor"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Set up an S3 bucket
&lt;/h2&gt;

&lt;p&gt;If you don't already have an S3 bucket, you'll need to set one up. You can find a get started guide here: &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/GetStartedWithS3.html" rel="noopener noreferrer"&gt;Getting Started with Amazon S3&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Your S3 bucket will need to have &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/enabling-cors-examples.html" rel="noopener noreferrer"&gt;cross-origin access enabled&lt;/a&gt;. The following cross-origin policy is sufficient to allow uploads from browsers – you can paste it into the CORS policy text box in the S3 console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="nl"&gt;"AllowedHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"AllowedMethods"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PUT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"AllowedOrigins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ExposeHeaders"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need an &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/create-access-key/" rel="noopener noreferrer"&gt;AWS Access Key&lt;/a&gt; and a &lt;a href="https://aws.amazon.com/blogs/security/wheres-my-secret-access-key/" rel="noopener noreferrer"&gt;Secret Access Key&lt;/a&gt; that has permission to write to your S3 bucket.&lt;/p&gt;

&lt;p&gt;Save your access keys in your app's &lt;a href="https://anvil.works/docs/security/encrypting-secret-data?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;App Secrets&lt;/a&gt;. Store the AWS access key ID in a secret named &lt;code&gt;aws_key_id&lt;/code&gt; and the secret key in a secret named &lt;code&gt;aws_secret_key&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil.works%2Fdocs%2Fhow-to%2Fimg%2Fupload-large-files-to-s3%2Faws-app-secrets.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil.works%2Fdocs%2Fhow-to%2Fimg%2Fupload-large-files-to-s3%2Faws-app-secrets.png" alt="AWS keys added as App Secrets in an Anvil app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Add the Uppy widget to the app
&lt;/h2&gt;

&lt;p&gt;Uppy is a third-party JavaScript that displays a form where users can drag and drop files to upload to S3. We can add the widget to an Anvil app using &lt;a href="https://anvil.works/docs/client/javascript#using-native-javascript-libraries?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;Native Libraries&lt;/a&gt; and the &lt;a href="https://anvil.works/docs/client/javascript?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;&lt;code&gt;anvil.js&lt;/code&gt; module&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;First, add the following line to your app's Native Libraries:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://releases.transloadit.com/uppy/v3.3.1/uppy.min.css"&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, go to the Design View of your Form, add a ColumnPanel, and name it &lt;code&gt;uppy_target&lt;/code&gt;. This is where the Uppy widget will go. &lt;/p&gt;

&lt;p&gt;Switch to Code View and add the following import statements to your Form's code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anvil.js&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;import_from&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;get_dom_node&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anvil.js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll need to set up two functions for the Uppy widget to use: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;get_upload_parameters&lt;/code&gt; will return AWS credentials from a Server Module that will give Uppy temporary permission to access the S3 bucket. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;on_complete&lt;/code&gt; will be called by Uppy when the upload completes, whether or not it's successful.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For now, the functions won't do anything, but we'll fix that in Steps 4 and 5.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_upload_parameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_descr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This function is called to get the pre-signed URL
&lt;/span&gt;    &lt;span class="c1"&gt;# for the S3 upload.
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;


  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# This function is called when the upload is finished,
&lt;/span&gt;    &lt;span class="c1"&gt;# successful or not.
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, in the Form's &lt;code&gt;__init__&lt;/code&gt; method, add the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="n"&gt;mod&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;import_from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://releases.transloadit.com/uppy/v3.3.1/uppy.min.mjs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uppy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Uppy&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dashboard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inline&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get_dom_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uppy_target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uppy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AwsS3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;getUploadParameters&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_upload_parameters&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uppy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;complete&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_complete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you run the app now, you'll see an empty Uppy uploader. Nothing will happen if you try to upload a file because we haven't set up the &lt;code&gt;get_upload_parameters&lt;/code&gt; function yet. In the next step, we'll use &lt;code&gt;boto3&lt;/code&gt; to return the AWS credentials we need.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil.works%2Fdocs%2Fhow-to%2Fimg%2Fupload-large-files-to-s3%2Fuppy-widget.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil.works%2Fdocs%2Fhow-to%2Fimg%2Fupload-large-files-to-s3%2Fuppy-widget.png" alt="The uppy widget added in an app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: Get a presigned URL from S3 using boto3
&lt;/h2&gt;

&lt;p&gt;The AWS access key we set up in Step 2 will provide access to S3, but it's a powerful credential for this very reason. We don't want to &lt;a href="https://anvil.works/docs/security#client-vs-server-code?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;expose it to the client&lt;/a&gt; and give anyone unlimited access to our S3. &lt;/p&gt;

&lt;p&gt;Instead, we can generate a presigned URL, which grants temporary access to upload one file to the target S3 bucket. We can use the &lt;code&gt;boto3&lt;/code&gt; library from a Server Module to generate the presigned URL.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We can trust the server code with powerful AWS credentials, because we have control over the server. Because the client is controlled by anyone who visits our app, we can't trust it with anything except a time-limited, single-purpose presigned URL.  Read more about client and server security &lt;a href="https://anvil.works/articles/client-vs-server?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In a Server Module, add the required import statements and set up variables for your S3 bucket and AWS region:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SystemRandom&lt;/span&gt;

&lt;span class="n"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;anvil-upload-demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your S3 bucket name
&lt;/span&gt;&lt;span class="n"&gt;REGION_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eu-west-2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="c1"&gt;# Replace with your AWS region
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we'll need to create a function to generate the presigned URL and make it callable from the client. This function will be called in the &lt;code&gt;get_upload_parameters&lt;/code&gt; function we created in the previous step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;s3_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;aws_access_key_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws_key_id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;aws_secret_access_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws_secret_key&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REGION_NAME&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Because the filename is user-supplied, we use the current date and a random string to ensure
&lt;/span&gt;  &lt;span class="c1"&gt;# a unique filename for each upload
&lt;/span&gt;  &lt;span class="n"&gt;random_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SystemRandom&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ascii_uppercase&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;filename_to_upload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;random_string&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

  &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_presigned_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BUCKET_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename_to_upload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Return the data in the format Uppy needs
&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 5: Provide the presigned URL to Uppy
&lt;/h2&gt;

&lt;p&gt;When Uppy is ready to upload a file, it will call the &lt;code&gt;get_upload_parameters&lt;/code&gt; function. We can now change the function so that it calls the &lt;code&gt;get_presigned_url&lt;/code&gt; server function and returns the results (the pre-signed URL and associated data) to Uppy.&lt;/p&gt;

&lt;p&gt;Because we're using this function as a callback from Javascript, we need to add the &lt;a href="https://anvil.works/docs/client/javascript/accessing-javascript#using-python-functions-as-callbacks?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;&lt;code&gt;@anvil.js.report_exceptions&lt;/code&gt; decorator&lt;/a&gt;. This makes sure that if the code throws an exception, it appears correctly in the Anvil console.&lt;/p&gt;

&lt;p&gt;Go back to your Form's client code and update &lt;code&gt;get_upload_parameters&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="nd"&gt;@anvil.js.report_exceptions&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_upload_parameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_descr&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_presigned_url&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;file_descr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;file_descr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Provided you've configured your bucket and access keys correctly, you should now be able to upload files to S3. &lt;/p&gt;

&lt;p&gt;Optionally, we can configure the &lt;code&gt;on_complete&lt;/code&gt; function to report feedback from Uppy. We can add this to an alert to display to the user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="nd"&gt;@anvil.js.report_exceptions&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;successful&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;successful&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nf"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;successful&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; succeeded, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Fblog%2Fupload-large-files-to-s3%2Fs3-upload-finished-app.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Fblog%2Fupload-large-files-to-s3%2Fs3-upload-finished-app.gif" alt="The final app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's it!
&lt;/h2&gt;

&lt;p&gt;That's all we need to upload files directly from our user's browser into S3.&lt;/p&gt;

&lt;p&gt;You can copy the sample app &lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/direct-s3-upload#clone:HEQEC5XZDJO3DONU%3dIT2JKSLWJOXJKFPX3SMLUBCI"&gt;here&lt;/a&gt;. Of course, you'll need your own S3 bucket and access keys for it work.&lt;/p&gt;

&lt;p&gt;For our next steps, consult the &lt;a href="https://uppy.io/docs/" rel="noopener noreferrer"&gt;Uppy documentation&lt;/a&gt; for how to customise the Uppy upload widget, or the &lt;a href="https://boto3.amazonaws.com/v1/documentation/api/latest/index.html" rel="noopener noreferrer"&gt;boto3 documentation&lt;/a&gt; for how to handle your file now it's safely uploaded into S3.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using other cloud storage services
&lt;/h3&gt;

&lt;p&gt;Amazon S3 isn't the only cloud storage service out there. But because it was the first, a lot of other services use the same API. You can adapt the sample code from this example to use other services, such as Cloudflare R2, DigitalOcean Spaces or Backblaze B2.&lt;/p&gt;

&lt;p&gt;Just check your preferred service's documentation for how to use it with &lt;code&gt;boto3&lt;/code&gt;. (For example, here's &lt;a href="https://developers.cloudflare.com/r2/examples/boto3/" rel="noopener noreferrer"&gt;CloudFlare's page on configuring boto3&lt;/a&gt; to work with their R2 service.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Build your own app with Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/direct-s3-upload"&gt;Sign up for Anvil!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>4 Streamlit Alternatives for Building Python Data Apps</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Wed, 08 Feb 2023 15:28:45 +0000</pubDate>
      <link>https://dev.to/anvil/4-streamlit-alternatives-for-building-python-data-apps-4797</link>
      <guid>https://dev.to/anvil/4-streamlit-alternatives-for-building-python-data-apps-4797</guid>
      <description>&lt;p&gt;&lt;a href="https://streamlit.io/" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt; is a library for turning Python scripts into shareable web applications. Streamlit apps are designed for the data science and machine learning community and make it easy to build dashboards and interactive ML models. What makes Streamlit so popular is its simplicity. Using the Streamlit library is quick and intuitive, which means you can create data apps without needing any web development experience. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8yl2pzpf2135renb0emh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8yl2pzpf2135renb0emh.png" alt="An example Streamlit application showing data on New York City Uber pickups" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While Streamlit is a great tool, it can fall short depending on your use case. The layout and styling of Streamlit apps is pretty rigid, so unless you're willing to write some HTML, JS and CSS, all your apps will pretty much look the same. Streamlit also has &lt;a href="https://medium.com/@ramiromedina/like-streamlit-but-fast-enabling-low-latency-data-apps-948b95b098a2" rel="noopener noreferrer"&gt;speed issues&lt;/a&gt; becuase it reruns the Python script serving the app after every interaction on the frontend. &lt;/p&gt;

&lt;p&gt;While Streamlit claims "instant deployment", this isn't quite true. Although &lt;a href="https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app" rel="noopener noreferrer"&gt;deploying an app on Streamlit Cloud&lt;/a&gt; isn't difficult, it takes &lt;a href="https://docs.streamlit.io/streamlit-cloud/get-started/deploy-an-app#watch-your-app-launch" rel="noopener noreferrer"&gt;several minutes&lt;/a&gt; each time you deploy a new change -- and that's not counting the time it takes the first time to connect up GitHub and Streamlit Cloud.&lt;/p&gt;

&lt;p&gt;If you're looking for an alternative to Streamlit, you have some options. In this article, we’ll be taking a look at four different alternatives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
Gradio: a Python library for building machine learning demos &lt;/li&gt;
&lt;li&gt;
Plotly Dash: a low-code framework for building data apps with the Plotly plotting library in Python, R or Julia&lt;/li&gt;
&lt;li&gt;
Panel: a Python library for creating flexible dashboards and web apps&lt;/li&gt;
&lt;li&gt;
Anvil: a platform for building full-stack web apps in Python&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Don't have the time? Skip the details of each alternative, and go straight to the summary.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Gradio
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://gradio.app/" rel="noopener noreferrer"&gt;Gradio&lt;/a&gt; is probably the most similar alternative to Streamlit on this list. Like Streamlit, Gradio is a Python library for turning datasets and machine learning models into interactive web UIs. The main difference is that Gradio is designed more for machine learning demos whereas Streamlit is designed for creating data dashboards.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fga5itckzt57ub5j7s24s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fga5itckzt57ub5j7s24s.png" alt="Image description" width="800" height="479"&gt;&lt;/a&gt;&lt;br&gt;An example Gradio application. Find it &lt;a href="https://colab.research.google.com/drive/12dowmDMfc6R64fAnua0ocGraHCm_jzIo?usp=sharing" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;East to use ML Models&lt;/strong&gt;: Gradio is now part of &lt;a href="https://huggingface.co/" rel="noopener noreferrer"&gt;Hugging Face&lt;/a&gt;, and the Hugging Face integration makes it sleek and simple to load ML models and datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy to share your apps&lt;/strong&gt;: It's easy to share your apps. You can quickly generate a link to send to other users, but this link expires after 72 hours and runs all the code on your own machine. To share your app, you'll need to host it someplace else, like &lt;a href="https://huggingface.co/spaces" rel="noopener noreferrer"&gt;Hugging Face Spaces&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Embed in notebooks&lt;/strong&gt;: Gradio can be embedded in a Python notebook. This makes it easy to share and interact with code. &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dated appearance&lt;/strong&gt;: Gradio apps have a very basic and dated look. It is possible to customize them, but you'll need to know how to write some CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Out-of-the-box cloud hosting options are limited&lt;/strong&gt;: The simplest way to host your Gradio app is on Hugging Face Spaces, but apps hosted on Spaces are very clearly hosted within the Hugging Face website, which makes it hard to brand your app for yourself or your company. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not standalone apps&lt;/strong&gt;: Gradio apps work best when embedded in another webpage, unlike Streamlit apps which work well as standalone apps.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Plotly Dash
&lt;/h2&gt;

&lt;p&gt;Plotly is a plotting library, and &lt;a href="https://dash.plotly.com/" rel="noopener noreferrer"&gt;Dash&lt;/a&gt; is their open-source framework for building data apps with Python, R or Julia. (Dash also has an &lt;a href="https://plotly.com/dash/" rel="noopener noreferrer"&gt;Enterprise version&lt;/a&gt;, but we'll focus on the open-source library here.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjvk0ucfm0umqdpyiftb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvjvk0ucfm0umqdpyiftb.png" alt="Screenshot of a Plotly Dash app showing data on NYC Uber pickups" width="800" height="496"&gt;&lt;/a&gt;&lt;br&gt;An example Dash Enterprise application. Find it &lt;a href="https://dash.gallery/dash-uber-rides-demo/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Faster than Streamlit&lt;/strong&gt;: Dash only needs to run the functions that are called while interacting with the app. With Streamlit, the entire script is re-run with every interaction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Work in Jupyter notebooks&lt;/strong&gt;: If you prefer working in a Jupyter notebook, the &lt;a href="https://pypi.org/project/jupyter-dash/" rel="noopener noreferrer"&gt;&lt;code&gt;jupyter-dash&lt;/code&gt;&lt;/a&gt; library lets you run your dashboards within a Jupyter notebook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;More flexible design&lt;/strong&gt;: If you know some HTML and CSS, you can more easily style your Dash apps how you want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not limited to Python&lt;/strong&gt;: Dash is also a framework for R and Julia, so you are not limited to building apps in Python.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Limited layout options without HTML&lt;/strong&gt;: Dash has a Python wrapper around HTML for building UIs, but you still need an understanding of HTML in order to use it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Larger learning curve&lt;/strong&gt;: Dash is a bit more complicated than Streamlit (you'll need to use &lt;a href="https://dash.plotly.com/basic-callbacks" rel="noopener noreferrer"&gt;"callbacks"&lt;/a&gt; and some HTML, for example), so it isn't as easy to get started with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mostly Limited to Plotly&lt;/strong&gt;: Dash is a Plotly product, so the framework is designed to work with that plotting library. Technically, you can use Dash with other plotting libraries but not as easily and smoothly as Plotly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Panel
&lt;/h2&gt;

&lt;p&gt;Like the previous three alternatives, &lt;a href="https://panel.holoviz.org/" rel="noopener noreferrer"&gt;Panel&lt;/a&gt; is an open-source Python library for creating interactive dashboard web apps. Panel is extremely flexible, allowing you to use any plotting library you like. Like Gradio but unlike Streamlit, you can use Panel in Jupyter notebooks. Panel dashboards can also be deployed as standalone web apps, but like Plotly Dash, you'll need to set up a server to deploy it yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falkaakzhbxqfusl67ajk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Falkaakzhbxqfusl67ajk.png" alt="Screenshot of a Panel app showing data on NYC taxi rides" width="800" height="439"&gt;&lt;/a&gt;&lt;br&gt;An example Panel application. Find it &lt;a href="https://nyc-taxi.pyviz.demo.anaconda.com/dashboard" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Work in Jupyter notebooks&lt;/strong&gt;: Panel integrates well with Jupyter notebooks, if that's how you prefer to write code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Faster UI&lt;/strong&gt;: Unlike Streamlit, Panel does not re-run the entire Python script with every interaction, which means the UI can update much faster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use any plotting library&lt;/strong&gt;: Panel is also not tied to any specific plotting library (but Streamlit includes integrations with all of the most popular Python plotting libraries).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start with a template&lt;/strong&gt;: Panel has some templates to start with. Although they are not very modern, they make it easier to vary the look of apps than Streamlit does.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dated appearance&lt;/strong&gt;: By default, Panel apps look extremely boring and dated even when using a template. Changing the styling of Panel apps requires writing CSS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Larger learning curve&lt;/strong&gt;: Panel is not as simple and intuitive as Streamlit is when building more complex apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small community&lt;/strong&gt;: Panel has a smaller community, which means it can be harder to discuss and get help with using the library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No out-of-the-box cloud hosting option&lt;/strong&gt;: Like all of the options here, Panel makes it easy to run and host your apps locally, but if you want to deploy them to the cloud, you're on your own. They do provide a guide on &lt;a href="https://panel.holoviz.org/user_guide/Server_Deployment.html" rel="noopener noreferrer"&gt;different deployment options&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Anvil
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/articles/4-alternatives-streamlit"&gt;Anvil&lt;/a&gt; is not a Python library like the other alternatives, but an entire IDE for building and deploying full-stack Python web applications. Anvil has a drag-and-drop page designer, which makes it much easier to layout and style the UI of your apps than any other alternative on the list. Because Anvil is more than just a Python library, it has more bells and whistles than the other options, such as a built-in database, user authentication, version control and cloud hosting that takes a matter of seconds to set up. All of this also means that it can have a bit more of a learning curve than the other alternatives.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frswaul7xkg7vc3m2l0c3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frswaul7xkg7vc3m2l0c3.png" alt="Screenshot of an Anvil app showing data on NYC Uber rides" width="800" height="510"&gt;&lt;/a&gt;&lt;br&gt;An example Anvil application. Find it &lt;a href="https://uber-pickups-example.anvil.app/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.
  &lt;/p&gt;

&lt;h3&gt;
  
  
  Pros:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deploy apps to the cloud instantly&lt;/strong&gt;: Anvil has truly instant cloud deployment. After just two clicks and no waiting time, your app is deployed. Basic hosting is free, including private apps. On paid plans, you can also have multiple development environments and add your own custom domains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Design the layout&lt;/strong&gt;: The drag-and-drop UI designer makes it easier to layout and style unique dashboards without CSS and HTML (although you can use them if you want).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connect to a built-in database&lt;/strong&gt;: The &lt;a href="https://anvil.works/docs/data-tables?utm_source=crosspost:dev.to:/articles/4-alternatives-streamlit"&gt;built-in database&lt;/a&gt; means you can store, query and edit data all with Python. Anvil also has &lt;a href="https://anvil.works/docs/working-with-files/data-files?utm_source=crosspost:dev.to:/articles/4-alternatives-streamlit"&gt;file storage&lt;/a&gt; for connecting CSV datasets, ML models and other large files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plotly is built in&lt;/strong&gt;: The &lt;a href="https://anvil.works/docs/client/components/plots?utm_source=crosspost:dev.to:/articles/4-alternatives-streamlit"&gt;built-in Plotly integration&lt;/a&gt; makes it easy to build dashboards.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cons:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Larger learning curve&lt;/strong&gt;: Anvil has more of a learning curve than the other alternatives, which are just Python libraries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Some features require a paid plan&lt;/strong&gt;: The Plotly integration is available for free, but if you want to install other Python libraries such as Pandas or Matplotlib, you'll need a paid plan (starting at $15/mo, with a free trial).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://streamlit.io/" rel="noopener noreferrer"&gt;Streamlit&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Quick to learn, easy for simple internal-facing or personal projects&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weakness&lt;/strong&gt;: Can be slow to run and deploy, limited in features and flexibility&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://gradio.app/" rel="noopener noreferrer"&gt;Gradio&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Easily connect to HuggingFace packages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Doesn't work well as a standalone app, limited in features and flexibility, basic and dated appearance&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://dash.plotly.com/" rel="noopener noreferrer"&gt;Plotly Dash&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Apps are fast, work from a Jupyter notebook, can use R or Julia&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Larger learning curve, need to know some HTML to layout apps&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://panel.holoviz.org/" rel="noopener noreferrer"&gt;Panel&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Use any plotting or visualisation library no matter how niche, work from a Jupyter notebook&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Basic and dated appearance, larger learning curve, no out-of-the-box cloud hosting&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/articles/4-alternatives-streamlit"&gt;Anvil&lt;/a&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strengths&lt;/strong&gt;: Out-of-the-box instant cloud hosting , drag-and-drop UI builder, more features for complex apps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Weaknesses&lt;/strong&gt;: Larger learning curve, not all features are available for free&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Streamlit is a great tool for turning a Python script into an interactive web application. But it's not your only option for building Python data apps. &lt;a href="https://gradio.app/" rel="noopener noreferrer"&gt;Gradio&lt;/a&gt; is a good tool for ML-focused apps, especially if you want to embed them in a Jupyter Notebook. For building dashboards, &lt;a href="https://plotly.com/dash/" rel="noopener noreferrer"&gt;Plotly Dash&lt;/a&gt; gives you more flexibility over the layout of your app, while &lt;a href="https://panel.holoviz.org/" rel="noopener noreferrer"&gt;Panel&lt;/a&gt; is the best option for using more niche plotting libraries. But if you're looking to build more complex apps and want to have control over their layout and design, then &lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/articles/4-alternatives-streamlit"&gt;Anvil&lt;/a&gt; comes out on top.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Build Python Web Apps with Even More Power and Flexibility</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Tue, 31 Jan 2023 10:53:03 +0000</pubDate>
      <link>https://dev.to/anvil/build-python-web-apps-with-even-more-power-and-flexibility-7e9</link>
      <guid>https://dev.to/anvil/build-python-web-apps-with-even-more-power-and-flexibility-7e9</guid>
      <description>&lt;p&gt;At Anvil, our goal is to make it fast and easy to build powerful web apps &lt;a href="https://anvil.works/articles/python-gui-builder-web?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;entirely in Python&lt;/a&gt;. So we've made Anvil itself faster, easier to use, and more powerful than ever. The new Editor includes many of your most-requested features such as &lt;a href="https://anvil.works/docs/version-control-new-ide?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;built-in version control&lt;/a&gt; and  &lt;a href="https://anvil.works/docs/data-tables/multiple-databases?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;multiple databases&lt;/a&gt;. We've also given the Anvil Editor a complete makeover to give you a cleaner, more intuitive interface for creating your web apps. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Er6N0UgD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3saacm6s7u6q3in64913.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Er6N0UgD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3saacm6s7u6q3in64913.png" alt="Screen recording of the new Anvil Editor" width="880" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Start building today!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Keep reading to learn about some of the biggest updates that are part of this release. For more details on all the new features, check out our &lt;a href="https://anvil.works/docs/overview?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;documentation&lt;/a&gt;. And as always, head on over to the &lt;a href="https://anvil.works/forum?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Anvil Community Forum&lt;/a&gt; to say hello, tell us your thoughts and ask any questions.&lt;/p&gt;




&lt;h2&gt;
  
  
  Powerful New Features
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Version control and collaboration
&lt;/h3&gt;

&lt;p&gt;At the bottom of the Editor is a &lt;a href="https://anvil.works/docs/version-control-new-ide?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;version control tab&lt;/a&gt; that lets you create branches, commit changes, merge contributions and resolve conflicts, all without leaving Anvil. You can now easily work with colleagues to edit your app, and the new version control tools mean you won’t get in each other’s way. With Anvil, you can now easily share your work with others and collaborate on your apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--3EMlgieV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mseqhedbwm5nhg9b768.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--3EMlgieV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2mseqhedbwm5nhg9b768.png" alt="Screenshot of the bottom panel showing two different branches and a commit highlighted." width="838" height="237"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple deployment environments and databases
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://anvil.works/docs/deployment-new-ide/environments?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Deploy&lt;/a&gt; development, staging and production versions of your app on separate URLs with &lt;a href="https://anvil.works/docs/data-tables/multiple-databases?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;separate databases&lt;/a&gt;, Uplinks and more. It’s now easier than ever to build, test and deploy your web apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--L6WhYSn7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0a9ial2zj7kct6zo0see.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--L6WhYSn7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0a9ial2zj7kct6zo0see.png" alt="Image description" width="821" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--NXg3ZUCY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdf0i1je4fo415xawoza.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--NXg3ZUCY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rdf0i1je4fo415xawoza.png" alt="Image description" width="880" height="378"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive Consoles
&lt;/h3&gt;

&lt;p&gt;Debugging your apps has never been easier. The &lt;a href="https://anvil.works/docs/editor#bottom-panel?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;new App Console&lt;/a&gt; allows you to interact directly with your running app via a live Python terminal. Alternatively, launch a Server Console to connect directly to your chosen Python environment on the server. Ideal for prototyping, debugging and exploring &lt;a href="https://anvil.works/docs/server/custom-packages?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;custom packages you've installed&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_MOE7rGJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewbwo2z5o118f6btv2e0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_MOE7rGJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ewbwo2z5o118f6btv2e0.png" alt="Screenshot of the App Console" width="829" height="277"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  A More Intuitive Interface
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tabbed editing
&lt;/h3&gt;

&lt;p&gt;You can now open multiple forms and modules in separate tabs, and switch between them easily. Switch from writing client code to writing server code to editing your data tables to editing your assets and back again. Colour-coded tabs make it easy to keep track of what you're working on.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--z42qffZo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wugg43ft4qupmrfh81xx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--z42qffZo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wugg43ft4qupmrfh81xx.png" alt="Image description" width="823" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Split view
&lt;/h3&gt;

&lt;p&gt;With the new Anvil Editor, you can view your client code and UI design &lt;a href="https://anvil.works/docs/editor#form-editor?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;side-by-side&lt;/a&gt;. You can now write Python code and tweak your UI without the need to switch back and forth. You can now also run your app in split view so you can build your app and view your live app in the same screen.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_qixcCdw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62lcqiqho5fxqw26m2on.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_qixcCdw--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/62lcqiqho5fxqw26m2on.png" alt="Image description" width="880" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dark mode
&lt;/h3&gt;

&lt;p&gt;Do you know the power of the Dark Side? &lt;a href="https://anvil.works/docs/editor/look-and-feel?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;We've got you covered&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5dt-MvK_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nia7v5uoh126exg7r8q4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5dt-MvK_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nia7v5uoh126exg7r8q4.png" alt="Image description" width="880" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  And so much more...
&lt;/h2&gt;

&lt;p&gt;The new Anvil Editor has a lot more to offer, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Improved app list&lt;/strong&gt;: Easily see all your apps, sort by name or the date they were last edited. Star your most important apps to keep them at the top of the list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upgraded code editors&lt;/strong&gt;: The code editors in Anvil now have better syntax highlighting as well as faster and more powerful autocompletion for all Python, JS, HTML, and CSS files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static file storage&lt;/strong&gt;: Use Anvil's &lt;a href="https://anvil.works/docs/working-with-files/data-files?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Data Files&lt;/a&gt; to attach large datasets, ML models and other files to your app and access them through an intuitive API. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Improved app logging&lt;/strong&gt;: Searching through &lt;a href="https://anvil.works/docs/editor/app-logs?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;app logs&lt;/a&gt; is faster and more powerful than before. You can now search through your app's logs using regex and filter by session type and environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out &lt;a href="https://anvil.works/docs?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;our documentation&lt;/a&gt; for more details on all the new features. &lt;/p&gt;

&lt;p&gt;The best way to experience the new Anvil Editor is to try it out for yourself. Start building web apps with Anvil today!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Start building today!&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  New to Anvil?
&lt;/h2&gt;

&lt;p&gt;If you're new to Anvil, welcome! We are a platform for building full-stack web applications entirely in Python - no JavaScript, HTML, CSS required. Anvil is perfect for developers of all skills levels, whether you're a beginner just getting started with web development or an experienced developer looking for a faster, more efficient way to build web apps.  &lt;/p&gt;

&lt;p&gt;Try the new Anvil Editor today and experience the power and flexibility of building web apps in Python. &lt;a href="https://anvil.works/sign-up?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Sign up&lt;/a&gt; for a free account and &lt;a href="https://anvil.works/build?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;start building&lt;/a&gt; your own web app in minutes. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://anvil.works/learn/tutorials/feedback-form?utm_source=crosspost:dev.to:/blog/new-editor-release"&gt;Start with a quick tutorial!&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Task Manager App Built with Python</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Tue, 15 Nov 2022 11:18:43 +0000</pubDate>
      <link>https://dev.to/anvil/a-task-manager-app-built-with-python-417o</link>
      <guid>https://dev.to/anvil/a-task-manager-app-built-with-python-417o</guid>
      <description>&lt;h1&gt;
  
  
  Building a task management app with Anvil
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Anvil&lt;/a&gt; is a powerful and versatile tool for building full-stack web applications. With Anvil, you can &lt;a href="https://anvil.works/blog/build-a-web-ui-with-python?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;quickly and easily create your UI&lt;/a&gt; and write Python code to control the front-end and back-end of your app. Creating apps with Anvil is fast and flexible, making it well-suited for building &lt;a href="https://anvil.works/for/internal-tools?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;internal tools&lt;/a&gt;. This simple but powerful task management app demonstrates some of what you can do with Anvil. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqwaxhqu7i0exi1r7bmj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkqwaxhqu7i0exi1r7bmj.png" alt="Opening and editing a project in the task management app"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the source code and check out the app for yourself: &lt;a href="https://anvil.works/build#clone:ILO62UVSCDQLTFHF%3dPJNDVGDLQSB7GG4RAQLSNHXA" rel="noopener noreferrer"&gt;https://anvil.works/build#clone:ILO62UVSCDQLTFHF%3dPJNDVGDLQSB7GG4RAQLSNHXA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://anvil.works/beta-docs/client/ui?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;the app’s UI&lt;/a&gt; with Anvil’s drag-and-drop editor and by writing front-end Python code. The back-end is also written in Python code in a &lt;a href="https://anvil.works/beta-docs/server?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;secure Server Module&lt;/a&gt;. The app also uses Anvil's built-in database system and user management service. Keep reading to learn more about how I built the task manager app with Python and Anvil.&lt;/p&gt;




&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;The app is a simple task manager that allows you to create and manage projects. Each project is a configurable table of tasks where users can give tasks a priority level, add checkboxes and assign other users to tasks. The table of tasks is displayed using a &lt;a href="https://anvil.works/beta-docs/client/components/data-grids?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Data Grid&lt;/a&gt;. Users can click on the cells in the table to edit their contents. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptlqrl62tinamgk164bg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fptlqrl62tinamgk164bg.gif" alt="Creating a new project and adding a column."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the main page, there is a board view of all the projects. Here, you can add comments to or delete projects. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kusnz8ruziu2vo7uhug.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4kusnz8ruziu2vo7uhug.gif" alt="Adding a comment and deleting a project"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  How it's built
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Building the database
&lt;/h3&gt;

&lt;p&gt;Every Anvil app comes with a &lt;a href="https://anvil.works/beta-docs/data-tables?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;built-in database system&lt;/a&gt; built on top of PostgreSQL. Anvil's Data Tables have a GUI for creating, editing and deleting tables, but tables can also be edited via Python code. To build my task manager app, I first needed to define my database schema.&lt;/p&gt;

&lt;p&gt;I needed a way to store the columns in each user-created project table and the data in each row. I did this by first creating a table to store the name and type of all the columns in the projects. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyn2338fle8nspricq6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftyn2338fle8nspricq6j.png" alt="The Columns Data Table stores the title and type of all columns in all projects"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, I created a table to store all the projects. This table has a column for the project name and a column that &lt;a href="https://anvil.works/beta-docs/data-tables/links-between-tables?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;links to columns table&lt;/a&gt;. This connects each row in the Columns Data Table with its corresponding project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhmwvtmq099sc0pceclb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhmwvtmq099sc0pceclb.png" alt="The Projects Data Table stores the name of each project and links to the Columns Data Table"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, a third Data Table stores the data for each row in all the projects. This table has a column linking back to Projects Data Table and a column for the row data, stored in a simple dictionary.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkg0e6dsz74q7ew5g7id.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvkg0e6dsz74q7ew5g7id.png" alt="The Rows Data Table links to a row from the Projects table and stores the data for each row in all the projects"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing back-end Python
&lt;/h3&gt;

&lt;p&gt;Anvil’s &lt;a href="https://anvil.works/beta-docs/server?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Server Modules&lt;/a&gt; are a full server-side Python environment. Server Modules cannot be edited or seen by the user, so we can &lt;a href="https://anvil.works/beta-docs/security#client-vs-server-code?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;trust them&lt;/a&gt; to do what we tell them. By default, the Data Tables in an Anvil app are not editable by users, but they can be accessed from a Server Module. In the task manager app, any updates to the Data Tables occur in server-side functions within a Server Module. These functions can be called from client-side Forms using just one line of code.&lt;/p&gt;

&lt;p&gt;For example, when a new column is added to a Data Grid, a new row is added to the Columns Data Table and that new row is then linked to the project in the Projects Data Table.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@anvil.server.callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_column_to_db&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#project is a row from the Projects Data Table
&lt;/span&gt;    &lt;span class="c1"&gt;#add a new row to the Columns Data Table
&lt;/span&gt;    &lt;span class="n"&gt;column_row&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;#link the new column to Projects
&lt;/span&gt;    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;columns&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;column_row&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;column_row&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@anvil.server.callable&lt;/code&gt; decorator means that this function can &lt;a href="https://dev.to/beta-docs/server#calling-server-functions-from-client-code"&gt;easily be called from client-side code&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;add_column_to_db&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Creating the UI with client-side Python
&lt;/h3&gt;

&lt;p&gt;I &lt;a href="https://anvil.works/beta-docs/client/ui?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;created the app’s UI&lt;/a&gt; by using both Anvil’s drag-and-drop UI builder and by writing Python code to dynamically generate UI elements. For example, to create the project view page of my app, I dragged a Data Grid onto the page which is then used to display the table of tasks for each project:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58k4d3on4dns99xjytv4.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58k4d3on4dns99xjytv4.gif" alt="Dragging-and-dropping a Data Grid from the Toolbox onto the ProjectView Form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The drag-and-drop designer makes building the front-end of your app a breeze, but sometimes you’ll want to build the front-end in code instead. For example, in the left navigation menu of my task management app, there are &lt;a href="https://anvil.works/beta-docs/client/components/basic#link?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Links&lt;/a&gt; to all the projects in the app. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2kxjqvfalr70e7dkt5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv2kxjqvfalr70e7dkt5r.png" alt="Adding a new project from the left navigation menu then renaming it. The links in the menu are cleared and regenerated when the project is renamed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These Links are dynamically generated via code and updated anytime a project is created, deleted or renamed. Below is the Python function that searches the database for projects and creates Links for each project. The function is written in a &lt;a href="https://anvil.works/beta-docs/client?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;client-side Form&lt;/a&gt; that runs in the user’s browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_project_links&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#clear out the container that the links are in
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;link_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;#get a list of the projects from the database
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;get_projects&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="c1"&gt;#create a link
&lt;/span&gt;      &lt;span class="n"&gt;link&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;project_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;#set up a event handler that opens the project when the link is clicked
&lt;/span&gt;      &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_event_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;click&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open_project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="c1"&gt;#add the link to the container
&lt;/span&gt;      &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;link_panel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anvil has many pre-built components that you can add to your apps, but you can also create your own component types using &lt;a href="https://anvil.works/beta-docs/client/custom-components?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Custom Components&lt;/a&gt;. In my task manager app, I created a Custom Component that allows users to edit the text of the component by clicking on it. I reused this Custom Component throughout the app to make the contents of the project tables and the name of the projects user-editable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Fexamples%2Ftask-manager-app%2Feditable-link.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Fexamples%2Ftask-manager-app%2Feditable-link.gif" alt="Clicking on the Custom Component allows the text to be edited. Pressing enter saves the new text."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding the User Management Service
&lt;/h3&gt;

&lt;p&gt;Anvil also provides built-in &lt;a href="https://anvil.works/beta-docs/users?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;user management&lt;/a&gt; that handles signup, login and user permissions. I added the Users service to my task management app so that team members can log in and work on projects together. Adding the Users service to an Anvil app automatically creates a &lt;a href="https://anvil.works/beta-docs/users/the_users_table?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Users table&lt;/a&gt; where you can manage access to your app. Since the Users table is an ordinary Data Table, I also used it to create a component for assigning tasks to users.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpmfw6j6st75zqolmhyc.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjpmfw6j6st75zqolmhyc.gif" alt="Assigning a task to a user"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anvil handles the login process, including single-sign-on with Google, Microsoft or your corporate SAML provider. I just needed to add &lt;code&gt;anvil.users.login_with_form()&lt;/code&gt; to my app so that users are presented with a login form on startup.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zjn8ge2v9gjngsj44h6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1zjn8ge2v9gjngsj44h6.png" alt="A login form for signing into the app"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;Clone the app to check out the source code and try it for yourself: &lt;a href="https://anvil.works/build#clone:ILO62UVSCDQLTFHF%3dPJNDVGDLQSB7GG4RAQLSNHXA" rel="noopener noreferrer"&gt;https://anvil.works/build#clone:ILO62UVSCDQLTFHF%3dPJNDVGDLQSB7GG4RAQLSNHXA&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re new to Anvil, why not try a &lt;a href="https://anvil.works/learn/tutorials/feedback-form?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;5-minute tutorial&lt;/a&gt; to learn more about building web apps with nothing but Python? Or if you’re an experienced Anvil user, why not extend the app? Here is some inspiration for features to add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Make the board view more informative.&lt;/strong&gt; Try displaying the number of tasks in each project on the card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add deadlines.&lt;/strong&gt; Use a &lt;a href="https://anvil.works/beta-docs/client/components/basic#datepicker?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Date Picker&lt;/a&gt; component to allow users to set a deadline on a project. If the deadline has passed, make the colour of the deadline red. Expand this even further by adding a new type of column to project tables that lets you set a date for each task.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archive tasks.&lt;/strong&gt; Add a function that lets users archive tasks without deleting them. They shouldn’t show up in the normal table but still be viewable somehow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow multiple tables per project&lt;/strong&gt;. Right now, the app only allows one table per project. Allow users to add or delete tables to a project. Think about how this would change your database structure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Show us what you’ve built! Have you created a cool internal tool with Anvil or extended this Task Manager app? Let us know! We’d love to see what you’ve built on our &lt;a href="https://anvil.works/forum/c/show-and-tell/6?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Show and Tell Community Forum&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Want to dig into how this app works? Our tutorials show you all the pieces this app is made from, and more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://anvil.works/learn/tutorials/feedback-form?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Tutorial: Build a Simple Feedback Form&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://anvil.works/learn/tutorials/dashboard?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Tutorial: Data Dashboard&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://anvil.works/learn/tutorials/database-backed-apps?utm_source=crosspost:dev.to:/learn/examples/task-manager-app"&gt;Tutorial: Build Database-Backed Apps&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to Style Anvil Apps with CSS</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Fri, 13 May 2022 16:05:54 +0000</pubDate>
      <link>https://dev.to/anvil/how-to-style-anvil-apps-with-css-3mn8</link>
      <guid>https://dev.to/anvil/how-to-style-anvil-apps-with-css-3mn8</guid>
      <description>&lt;h3&gt;
  
  
  About Anvil
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://anvil.works?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just build it all in Python. Get started with one of our tutorials or check out an example app. For more help, we have detailed developer documentation and a friendly Community Forum.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Using CSS in Anvil
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://anvil.works?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Anvil&lt;/a&gt; lets you build web apps entirely in Python. You can build your UI by &lt;a href="https://anvil.works/articles/drag-and-drop-builder?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;dragging and dropping&lt;/a&gt; Python components and styling them with Python code. But if you want to have even more control over the appearance of your Anvil apps, you can use CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS" rel="noopener noreferrer"&gt;CSS&lt;/a&gt; is a language used to style web pages. This guide will provide an introduction to CSS and how we can use it to change the appearance of web pages. We'll see how we can target components on a web page with CSS and write rules to modify how those components look. You'll be able to test your skills with some challenges as we go along. We'll then experiment with CSS on the live &lt;a href="https://anvil.works?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Anvil homepage&lt;/a&gt; using the developer tools built into your browser. Then, we'll work through an example of using CSS to customize the appearance of an Anvil app with the Material Design theme.&lt;/p&gt;

&lt;p&gt;In this guide to using CSS in Anvil, we will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Take a look at HTML and its relationship to CSS
&lt;/li&gt;
&lt;li&gt;Examine CSS syntax in more detail, including:

&lt;ul&gt;
&lt;li&gt;Using selectors and classes&lt;/li&gt;
&lt;li&gt;Combining selectors&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Use the browser's developer tools on the Anvil homepage to:

&lt;ul&gt;
&lt;li&gt;Select an HTML element&lt;/li&gt;
&lt;li&gt;Inspect the HTML code&lt;/li&gt;
&lt;li&gt;Inspect the CSS code&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Learn more about using CSS to style Anvil apps, including: 

&lt;ul&gt;
&lt;li&gt;Applying CSS via Python&lt;/li&gt;
&lt;li&gt;Writing CSS in theme.css&lt;/li&gt;
&lt;li&gt;Using Anvil roles&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The best external resource for learning more about CSS and HTML is the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/" rel="noopener noreferrer"&gt;Mozilla Developer Network Web Docs&lt;/a&gt;. The MDN Web Docs are an open-source project that provide reference guides and tutorials for web languages and technologies. They have detailed and clear resources for beginners and advanced users. I will frequently be linking to the MDN Web Docs in this guide.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  HTML and CSS
&lt;/h2&gt;

&lt;p&gt;When we talk about &lt;strong&gt;front-end web development&lt;/strong&gt;, we are talking about all the code that is responsible for displaying the visual part of a website or web app. When you load a web page, all the code associated with displaying the page is loaded into your browser, which is where the code runs. You can actually inspect this code and make local changes to it, which we will do with the Anvil homepage later in this guide.&lt;/p&gt;

&lt;p&gt;In the traditional, non-Anvil, way of doing front-end development, &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML" rel="noopener noreferrer"&gt;HTML&lt;/a&gt; is used to define the elements that make up a web page. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS" rel="noopener noreferrer"&gt;CSS&lt;/a&gt; targets those elements and defines rules that dictate how they will appear on the page. When you drag-and-drop Anvil components onto a form, these components generate their own HTML. When you edit the component's properties, this generates CSS rules that target the HTML to style the component. &lt;/p&gt;

&lt;p&gt;HTML defines the elements on a page using "tags". The most basic HTML element is a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, which is just a generic container with no pre-defined styling. Very often, HTML elements are nested inside each other, just as in Anvil where we often nest components within &lt;a href="https://anvil.works/beta-docs/client/components/containers" rel="noopener noreferrer"&gt;containers&lt;/a&gt; like &lt;a href="https://anvil.works/beta-docs/client/components/containers#columnpanel" rel="noopener noreferrer"&gt;ColumnPanels&lt;/a&gt; and &lt;a href="https://anvil.works/beta-docs/client/components/containers#flowpanel" rel="noopener noreferrer"&gt;FlowPanels&lt;/a&gt;. For example, the following code defines a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; and adds a button that says 'Submit' to the page:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With CSS, we can target the button and change the way it looks. The following code targets all buttons on the page, gives them a pink border and a gray background color:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Next, we'll take a closer look at CSS and its syntax.&lt;/p&gt;

&lt;h2&gt;
  
  
  CSS Syntax
&lt;/h2&gt;

&lt;p&gt;With CSS, we can write rules that tell the browser what each component on our webpage should look like. CSS stands for &lt;strong&gt;Cascading Stylesheet&lt;/strong&gt;. 'Stylesheet' refers to the document where the CSS styling rules are written, and 'Cascading' refers to the order in which CSS applies those styling rules. A CSS rule has three parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Selector&lt;/strong&gt;: This 'selects' which HTML element or elements to apply the rule to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Property&lt;/strong&gt;: This is the feature of the element that will be targeted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Value&lt;/strong&gt;: Describes how the property will be rendered by the browser.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fab3kih9tj34c3ivvxzj6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fab3kih9tj34c3ivvxzj6.png" alt="Syntax of a CSS rule"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Selectors and classes
&lt;/h3&gt;

&lt;p&gt;In the previous example, we used CSS to target &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements. We did this by just writing the name of the tag and then defining the rule. This is called a &lt;strong&gt;type selector&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;More often, we will target elements by giving them a &lt;strong&gt;class&lt;/strong&gt;. Classes can be applied to as many HTML elements as we want. They are a way of applying the same style to multiple elements without repeating code. To add a class in HTML, we use the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class" rel="noopener noreferrer"&gt;&lt;code&gt;class&lt;/code&gt; attribute&lt;/a&gt;, and to use the &lt;strong&gt;class selector&lt;/strong&gt;, we use &lt;code&gt;.&lt;/code&gt; followed by the class name. &lt;/p&gt;

&lt;p&gt;Let's extend our previous example and give the button a class. Then, we can apply styling rules to that class. This way, we won't change every single button on the page, just the ones we want to target. Let's give the button a class called &lt;code&gt;submit&lt;/code&gt; and apply the same styling rules just to that class:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There are a number of different CSS selectors, which you can read about &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#basic_selectors" rel="noopener noreferrer"&gt;here&lt;/a&gt;, but the most important for this guide are type and class selectors.&lt;/p&gt;

&lt;h4&gt;
  
  
  Try it yourself
&lt;/h4&gt;

&lt;p&gt;Now it's your turn! Open the following code in CodePen and:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the second &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; a class called &lt;code&gt;cancel&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;In the CSS, target that class and give it a different background color. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;em&gt;The &lt;code&gt;background-color&lt;/code&gt; property can take some text-based values as well as hex and rgb values. Read more about &lt;code&gt;background-color&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/background-color" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/bcm628/embed/OJzQbYx?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Combining selectors
&lt;/h3&gt;

&lt;p&gt;Selectors can also be combined in different ways. Let's look at how we can use more complex selectors to target the elements that we want.&lt;/p&gt;

&lt;h4&gt;
  
  
  Multiple selectors
&lt;/h4&gt;

&lt;p&gt;Selectors can be grouped using a &lt;code&gt;,&lt;/code&gt; in order to apply the same rules to multiple selectors. In the example below, elements with the &lt;code&gt;submit&lt;/code&gt; or the &lt;code&gt;cancel&lt;/code&gt; class will have a font size of 16px:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.submit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.cancel&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Combining type and class selectors
&lt;/h4&gt;

&lt;p&gt;A class selector can come directly after a type selector (without a space in between) to select elements of that type with that class. For example, we can use the following code to select &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements with the &lt;code&gt;submit&lt;/code&gt; class (in case there are other types of elements that have the same class name):&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nc"&gt;.submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h4&gt;
  
  
  Selecting descendents
&lt;/h4&gt;

&lt;p&gt;Sometimes, we want to select elements that are nested inside other elements. For example, we might want to select buttons that are within a &lt;code&gt;card&lt;/code&gt; container but no other buttons. A space between two selectors will select the elements that are descendants of the first element, while a &lt;code&gt;&amp;gt;&lt;/code&gt; will select the elements that are a child (a direct descendant) of the first element. &lt;/p&gt;

&lt;p&gt;For example, the following HTML code defines a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; element with the class &lt;code&gt;card&lt;/code&gt;, which has another &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with the class &lt;code&gt;buttons-div&lt;/code&gt; nested inside. Inside that &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; are two &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"card"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"buttons-div"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Submit&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"cancel"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Cancel&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Because the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements are within the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; with the &lt;code&gt;card&lt;/code&gt; class, we can select them with the following CSS:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;However, the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;s are not children of the &lt;code&gt;card&lt;/code&gt; element because they are not directly nested within the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, but they are children of the &lt;code&gt;buttons-div&lt;/code&gt; element. The first rule below will not select the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt;s whereas the second rule will:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="c"&gt;/* This won't select the buttons because they aren't children of the "card" &amp;lt;div&amp;gt; */&lt;/span&gt;
&lt;span class="nc"&gt;.card&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;/* This will select the buttons because they are children of the "buttons-div" &amp;lt;div&amp;gt; */&lt;/span&gt;
&lt;span class="nc"&gt;.buttons-div&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1px&lt;/span&gt; &lt;span class="nb"&gt;solid&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For more on combining CSS selectors, see the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors#combinators" rel="noopener noreferrer"&gt;MDN web docs&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Try it yourself
&lt;/h4&gt;

&lt;p&gt;Now it's your turn to combine CSS selectors to target elements. For the following challenges, open the code in CodePen, and don't add any new classes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Select the second &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; and give it a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color" rel="noopener noreferrer"&gt;&lt;code&gt;color&lt;/code&gt;&lt;/a&gt; of pink. But keep the first &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; the way it is.&lt;/li&gt;
&lt;li&gt;Give both the &lt;code&gt;submit&lt;/code&gt; and &lt;code&gt;clear&lt;/code&gt; buttons a &lt;code&gt;background-color&lt;/code&gt; of &lt;code&gt;lightblue&lt;/code&gt;, but leave the &lt;code&gt;cancel&lt;/code&gt; button the way it is.&lt;/li&gt;
&lt;li&gt;Give the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; within the &lt;code&gt;card&lt;/code&gt; a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/border" rel="noopener noreferrer"&gt;&lt;code&gt;border&lt;/code&gt;&lt;/a&gt; property of &lt;code&gt;1px solid pink&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/bcm628/embed/wvpyJyv?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Stuck? &lt;a href="https://codepen.io/bcm628/pen/jOYZBPO" rel="noopener noreferrer"&gt;Check out a solution&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  CSS properties
&lt;/h3&gt;

&lt;p&gt;There are many different CSS properties that affect the layout and appearance of HTML elements, but it's beyond the scope of this guide to go over them in detail. For a list of all CSS properties and their corresponding values, see the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Properties_Reference" rel="noopener noreferrer"&gt;MDN web docs&lt;/a&gt;. In general, the MDN docs are the best place to learn more about CSS properties because they are detailed, give visual examples and are easy to follow. &lt;/p&gt;

&lt;h2&gt;
  
  
  Using the developer tools
&lt;/h2&gt;

&lt;p&gt;Your browser has built-in developer tools to help you with front-end web development. These developer tools let you see all the code and files that are loaded into the browser when you open a web page. We can use the dev tools to inspect live code and make local edits, so they are an incredibly handy tool for debugging and experimenting with styling. &lt;/p&gt;

&lt;p&gt;Let's now use the dev tools to inspect the &lt;a href="https://anvil.works?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Anvil homepage&lt;/a&gt; and see how HTML and CSS works out in the wild. &lt;/p&gt;

&lt;h3&gt;
  
  
  Selecting an element
&lt;/h3&gt;

&lt;p&gt;When you load a web page, all the code used to display that page is loaded into the browser. This means that we can see the source code for any web page we can load in our browser, and we can make local changes to that code. Let's go to &lt;a href="https://anvil.works?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Anvil's homepage&lt;/a&gt; and inspect the source code.&lt;/p&gt;

&lt;p&gt;On the website, you can right click and choose 'Inspect' to bring up the browser's developer tools. (I use Google Chrome, but other browsers will have very similar dev tools). &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fik4079yc8juhgjtzzrrp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fik4079yc8juhgjtzzrrp.png" alt="Right-clicking on the homepage to inspect its code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the dev tools, click on 'Elements' to bring up the HTML and CSS for the page. Then click on the icon that looks like a square with a mouse cursor. This will allow us to select an element on the page and inspect its HTML and CSS.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuk799u3j0p8amjqjut26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuk799u3j0p8amjqjut26.png" alt="The Elements tab in the dev tools"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's select the link that says "Start with a quick tutorial" on the Anvil website. This will highlight the relevant HTML in the dev tools and show us the CSS associated with that element. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mfcxkzp7eiv1p6e4n2a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7mfcxkzp7eiv1p6e4n2a.png" alt="Selecting the tutorial link on Anvil's homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Inspecting the HTML
&lt;/h3&gt;

&lt;p&gt;This is the HTML responsible for displaying that link:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3l9nvhmvninze1uhs3t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe3l9nvhmvninze1uhs3t.png" alt="Screenshot of the dev tools showing the HTML code for a link on the Anvil homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we have an &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a" rel="noopener noreferrer"&gt;&lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, or anchor, element&lt;/a&gt;. Anchor elements are links, and the href attribute specifies the link's "target": usually, the URL to open when you click it The element also has a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Class_selectors" rel="noopener noreferrer"&gt;&lt;code&gt;class&lt;/code&gt; attribute&lt;/a&gt;. This particular element has two classes: &lt;code&gt;nowrap&lt;/code&gt; and &lt;code&gt;tutorial&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is also an &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; element nested inside the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; element. The SVG is displaying the arrow in the link. Because the &lt;code&gt;&amp;lt;svg&amp;gt;&lt;/code&gt; is nested directly inside the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;, we can say that it is a child of the &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inspecting the CSS
&lt;/h3&gt;

&lt;p&gt;In the dev tools, we can see the CSS being applied to the element. The first rule looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsr5zth70tzmlvlvpkpfv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsr5zth70tzmlvlvpkpfv.png" alt="Screenshot of the dev tools showing the CSS code for a link on the Anvil homepage"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first line is the &lt;strong&gt;selector&lt;/strong&gt;. This particular selector means "select the element with the &lt;code&gt;tutorial&lt;/code&gt; class that is a descendent of an element with the &lt;code&gt;sub-hero-links&lt;/code&gt; class which is a descendent of an element with &lt;em&gt;both&lt;/em&gt; the &lt;code&gt;col-hero&lt;/code&gt; and &lt;code&gt;home&lt;/code&gt; classes".&lt;/p&gt;

&lt;p&gt;The next line of the CSS rule is the &lt;strong&gt;property&lt;/strong&gt; we want to target and how it should be displayed. This particular rule is making the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color" rel="noopener noreferrer"&gt;&lt;code&gt;color&lt;/code&gt;&lt;/a&gt; of the element a light grey. &lt;/p&gt;

&lt;p&gt;We can actually modify the code directly in the dev tools. If I change the &lt;code&gt;color&lt;/code&gt; of the link to be &lt;code&gt;lightgreen&lt;/code&gt;, for example, we can see the change live in the browser. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxdwx8yjx46tg8x6qk7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwgxdwx8yjx46tg8x6qk7.gif" alt="Changing the color of the tutorial link from the browser"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is incredibly handy for styling apps. You can change and add CSS properties directly in the dev tools and immediately see the results. After playing with the CSS  styling in the browser, you can then go and actually change the source code of your app. The dev tools are also incredibly useful for debugging. If an element is not displaying how you think it should be, you can inspect it to see what CSS code is being applied to the element. Refreshing your browser will reload the page, and your local changes will be gone. This makes it easy to experiment without worrying about breaking the source code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using CSS in Anvil apps
&lt;/h2&gt;

&lt;p&gt;With Anvil, you can build the front-end of your web app entirely in Python. However, even when you are styling your apps in Python, your apps still use HTML and CSS. Anvil components generate HTML, and changing the properties of those components modifies the CSS for those components. In Anvil, we can change the appearance of components using Python via the &lt;a href="https://anvil.works/beta-docs/editor#properties-panel?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Properties Panel&lt;/a&gt; or &lt;a href="https://anvil.works/beta-docs/client/python?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;client code&lt;/a&gt;. Every Anvil app also has a stylesheet, so we can also write CSS for our app in the traditional way. This gives us more control over the appearance of an app.&lt;/p&gt;

&lt;p&gt;To see how CSS is used in Anvil apps, let's &lt;a href="https://anvil.works/new-build?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;create a new Anvil app&lt;/a&gt; and choose the Material Design theme.&lt;/p&gt;

&lt;h3&gt;
  
  
  Applying CSS via Python
&lt;/h3&gt;

&lt;p&gt;Anvil components have &lt;a href="https://anvil.works/docs/client/components#properties?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;properties&lt;/a&gt; that can be modified via the Editor or through Python code. When we modify a component's properties, CSS is applied to the component's HTML in a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/style" rel="noopener noreferrer"&gt;style attribute&lt;/a&gt;. To see this in action, let's drag and drop a Button component onto our Anvil app and change its background color to gray in the Properties Panel:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fusing-css%2Fadd-button.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fusing-css%2Fadd-button.gif" alt="Drag and drop a Button onto the form and change its  raw `background` endraw  in the Properties Panel."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can also change properties from code. Let's switch to code view and change the foreground color of our button in the &lt;code&gt;__init_&lt;/code&gt; of our Form code. &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Form1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form1Template&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Set Form properties and Data Bindings.
&lt;/span&gt;    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init_components&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;button_1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;foreground&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We can now run our app and inspect the page, just like we did with the Anvil homepage. If we select the button, we can see some HTML that looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;

&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-default to-disable"&lt;/span&gt; &lt;span class="na"&gt;ontouchstart=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="na"&gt;style=&lt;/span&gt;&lt;span class="s"&gt;"max-width: 100%; text-overflow: ellipsis; overflow: hidden; background-color: gray ; color: white;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;...&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Within the &lt;code&gt;style&lt;/code&gt; attribute, we can see both the &lt;code&gt;background-color&lt;/code&gt; property that we added in the Properties Panel and the &lt;code&gt;color&lt;/code&gt; property (&lt;code&gt;foreground&lt;/code&gt; in Anvil) that we added in the Form code. &lt;/p&gt;

&lt;h3&gt;
  
  
  Writing CSS in the stylesheet
&lt;/h3&gt;

&lt;p&gt;We can also target Anvil components and write CSS rules in the app's stylesheet. Every Anvil app has a stylesheet named &lt;code&gt;theme.css&lt;/code&gt; associated with it, which can be found in the App browser under "Assets". &lt;/p&gt;

&lt;p&gt;Because we created an Anvil app with the Material Design theme, our app's stylesheet is already populated with CSS rules. Let's open &lt;code&gt;theme.css&lt;/code&gt; and modify some of the code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tte04xikvyfh8noe6g0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5tte04xikvyfh8noe6g0.png" alt="Location of  raw `theme.css` endraw  in the App Browser"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's find the CSS code related to styling Buttons. &lt;code&gt;Ctrl + F&lt;/code&gt; and search for "button". This should land us around line 548. Here we can see some CSS that is applied to components with the &lt;code&gt;btn&lt;/code&gt; class. If we run our app and inspect the button again, we can see that it has a class called &lt;code&gt;btn&lt;/code&gt;. In this rule, let's adjust the &lt;code&gt;background-color&lt;/code&gt; to be something different:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.btn&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.btn-default&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.file-loader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;lightblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* This line is changed */&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, if we run the app, we can see that nothing has changed. Let's inspect the button to see what is going on. We can see the &lt;code&gt;background-color&lt;/code&gt; we added in &lt;code&gt;theme.css&lt;/code&gt; is crossed out, and instead the &lt;code&gt;background-color&lt;/code&gt; that we added in the Properties Panel is taking precedence. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftps3iqbs7weixd8c48n6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftps3iqbs7weixd8c48n6.png" alt="Code showing that the  raw `background-color: gray` endraw  property in the  raw `style` endraw  attribute is taking precedence."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When CSS properties conflict with each other, CSS has rules to determine which property is applied. Styling rules written in the stylesheet will be applied first, then rules written in the HTML will be applied. This is why our Button is &lt;code&gt;gray&lt;/code&gt; and not &lt;code&gt;lightblue&lt;/code&gt;. The &lt;code&gt;background-color: gray&lt;/code&gt; property written in the HTML will be applied last and therefore overrides the &lt;code&gt;background-color: lightblue&lt;/code&gt; property in &lt;code&gt;theme.css&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If CSS styling rules written in the stylesheet conflict, then the last rule will override preceding rules. This is why stylesheets are &lt;strong&gt;cascading&lt;/strong&gt;. For example, if we had the following code in a stylesheet (and no conflicting styling rules added to the HTML), then buttons would end up having a pink background:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;However, if rules have different selectors but could still conflict, then the most specific rule applies. Class selectors are more specific than type selectors. So in the following code, buttons with the &lt;code&gt;submit&lt;/code&gt; class would have a gray background color despite the rule coming first:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The cascade and specificity rules can be overridden by using &lt;code&gt;!important&lt;/code&gt;. &lt;code&gt;!important&lt;/code&gt; is applied to a property within a rule in order to make its value more important than any other conflicting value, but this is almost always a bad idea. We'll discuss why &lt;code&gt;!important&lt;/code&gt; should be avoided, but let's first see how it works by making the &lt;code&gt;background-color&lt;/code&gt; property we set in &lt;code&gt;theme.css&lt;/code&gt; &lt;code&gt;!important&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.btn&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.btn-default&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.file-loader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;lightblue&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If we run the app again, the button now has a light blue background. Inspecting the button shows that the &lt;code&gt;background-color&lt;/code&gt; set in the style attribute is crossed out this time. In practice, we should not use &lt;code&gt;!important&lt;/code&gt; here. Instead, we should either set the &lt;code&gt;background-color&lt;/code&gt; we want from the Properties Panel or remove the &lt;code&gt;background-color&lt;/code&gt; set there and set it in &lt;code&gt;theme.css&lt;/code&gt; without making it &lt;code&gt;!important&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwr7wpdae76edrqet7rs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbwr7wpdae76edrqet7rs.png" alt="The  raw `background-color: lightblue` endraw  property is now  raw `!important` endraw , so the Button is light blue."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;In general, &lt;strong&gt;using &lt;code&gt;!important&lt;/code&gt; should be avoided.&lt;/strong&gt; Occasionally, you may need to use it to override default styling rules that you don't have access to. (The example above is not one of these scenarios.) But overriding the cascade makes debugging difficult and will often cause other developers (and your future self) pain and confusion. Instead, try reordering rules and using more specific selectors. You can read more about specificity and &lt;code&gt;!important&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/Specificity" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Anvil Roles
&lt;/h3&gt;

&lt;p&gt;We can add CSS classes to Anvil components using Anvil &lt;a href="https://anvil.works/beta-docs/client/themes-and-styling/roles?utm_source=dev_to_css_guide" rel="noopener noreferrer"&gt;Roles&lt;/a&gt;. After you create a Role, you can write CSS rules in the app's stylesheet to define how the Role will affect components. You can apply the Role to a component through the Properties Panel or in code. &lt;/p&gt;

&lt;p&gt;Let's create a new Role in our app to apply to our Button. In the sidebar, click on "Theme", then choose "Roles". Click on "+ Add a new role" then choose a name for the role. I've named my role &lt;code&gt;submit&lt;/code&gt; and have restricted it so that it can only be applied to Button components. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fusing-css%2Fcreate-new-role.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fusing-css%2Fcreate-new-role.gif" alt="Creating a new Anvil Role called  raw `submit` endraw  and restrict it to Button components."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's now switch back to &lt;code&gt;Form1&lt;/code&gt; and give the Button the &lt;code&gt;submit&lt;/code&gt; Role. Let's also remove any properties we set in the Editor or in the Form code. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fusing-css%2Fadd-role.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fanvil-website-static.s3.eu-west-2.amazonaws.com%2Flearn%2Ftutorials%2Fusing-css%2Fadd-role.gif" alt="Giving the button the  raw `submit` endraw  role."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We should also change the code we altered in &lt;code&gt;theme.css&lt;/code&gt;. Right now, all buttons in our app will be light blue, but let's use the role we just defined to only make Buttons with the &lt;code&gt;submit&lt;/code&gt; Role light blue. So the code we changed in &lt;code&gt;theme.css&lt;/code&gt; should look like this again:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.btn&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.btn-default&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;.file-loader&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nt"&gt;label&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;border-radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;transparent&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c"&gt;/* this line is changed back */&lt;/span&gt;
  &lt;span class="err"&gt;...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Button now has the &lt;code&gt;submit&lt;/code&gt; Role, which means Anvil will give the component a CSS class. The class name will be the Role name with the prefix &lt;code&gt;anvil-role-&lt;/code&gt;. We can now go into &lt;code&gt;theme.css&lt;/code&gt; and write some styling rules for the &lt;code&gt;anvil-role-submit&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;At the very bottom of &lt;code&gt;theme.css&lt;/code&gt;, let's select the &lt;code&gt;anvil-role-submit&lt;/code&gt; class and change the background color, the font color and the font size:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.anvil-role-submit&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;lightblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run the app to see how the code worked. &lt;/p&gt;

&lt;p&gt;Our button doesn't quite look right, so we should inspect the app to see what is going on. If we select the element with the light blue background, we can see that it is a &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; that has the &lt;code&gt;anvil-role-submit&lt;/code&gt; class. Inside of this &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is the Button we actually want to target. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx63h8kk9mwnt9t0b77ag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx63h8kk9mwnt9t0b77ag.png" alt="Screenshot of the dev tools showing the HTML for the outer &amp;lt;div&amp;gt; element and the &amp;lt;button&amp;gt; nested inside"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's go back to &lt;code&gt;theme.css&lt;/code&gt; and modify the selector so we select the &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; nested inside the &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There are a few ways we can do this, but let's be as specific as possible and select &lt;code&gt;&amp;lt;button&amp;gt;&lt;/code&gt; elements that have the &lt;code&gt;btn&lt;/code&gt; class that are children of the &lt;code&gt;.anvil-role-submit&lt;/code&gt; class:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;

&lt;span class="nc"&gt;.anvil-role-submit&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="nc"&gt;.btn&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;background-color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;lightblue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="no"&gt;white&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;22px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's rerun our app to see if we've selected the correct element.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21c7ib5cebs3uhxn4692.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F21c7ib5cebs3uhxn4692.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Great, it worked! &lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this guide, we have gone over the basics of using CSS in Anvil apps. We briefly covered HTML and how it relates to CSS. We also looked at how to write CSS rules using selectors and properties. We've also seen how we can use the browser's developer tools to inspect code and make local changes. When using CSS in Anvil, we can edit the properties of Anvil components with Python or by writing CSS rules in the app's stylesheet. We can also define Anvil roles to add CSS classes to Anvil components.&lt;/p&gt;

&lt;p&gt;CSS is a very powerful tool, but we've only scratched the surface on what it can do. I highly recommend playing around with your browser's developer tools to inspect web pages and make local changes to the code. This is a great way to learn more about how CSS works. &lt;/p&gt;

&lt;p&gt;If you'd like to learn more about how to use CSS, check out my suggested resources below:&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;For a short tutorial to help you &lt;strong&gt;get started with CSS&lt;/strong&gt;, check out &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/Getting_started_with_the_web/CSS_basics" rel="noopener noreferrer"&gt;MDN's CSS basics&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For an &lt;strong&gt;introduction to HTML&lt;/strong&gt;, MDN have a &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/HTML/Introduction_to_HTML" rel="noopener noreferrer"&gt;good, easy-to-follow tutorial&lt;/a&gt; designed for beginners.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For a more &lt;strong&gt;in depth introduction to CSS&lt;/strong&gt;, MDN also have a &lt;a href="https://developer.mozilla.org/en-US/docs/Learn/CSS" rel="noopener noreferrer"&gt;good CSS tutorial&lt;/a&gt; that assumes no prior CSS knowledge but assumes you have completed their HTML tutorial.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For a &lt;strong&gt;full list of CSS properties&lt;/strong&gt; and examples of their use, check out the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Properties_Reference" rel="noopener noreferrer"&gt;MDN web docs&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you prefer more &lt;strong&gt;interactive tutorials&lt;/strong&gt;, I highly recommend &lt;a href="https://scrimba.com/" rel="noopener noreferrer"&gt;Scrimba&lt;/a&gt;. Their &lt;a href="https://scrimba.com/learn/htmlcss" rel="noopener noreferrer"&gt;HTML and CSS crash course&lt;/a&gt; is designed for beginners.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For &lt;strong&gt;articles on CSS topics&lt;/strong&gt; and cool tricks, see &lt;a href="https://css-tricks.com/" rel="noopener noreferrer"&gt;CSS Tricks&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>python</category>
      <category>tutorial</category>
      <category>webdev</category>
      <category>css</category>
    </item>
    <item>
      <title>Client vs Server Code in Anvil</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Mon, 04 Apr 2022 16:05:26 +0000</pubDate>
      <link>https://dev.to/anvil/client-vs-server-code-in-anvil-5bhc</link>
      <guid>https://dev.to/anvil/client-vs-server-code-in-anvil-5bhc</guid>
      <description>&lt;h3&gt;
  
  
  About Anvil
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://anvil.works?utm_source=dev_to_client_server"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;. Get started with one of our &lt;a href="https://anvil.works/learn/tutorials?utm_source=dev_to_client_server"&gt;tutorials&lt;/a&gt; or check out an &lt;a href="https://anvil.works/learn/examples"&gt;example app&lt;/a&gt;. For more help, we have detailed &lt;a href="https://anvil.works/beta-docs?utm_source=dev_to_client_server"&gt;developer documentation&lt;/a&gt; and a friendly &lt;a href="https://anvil.works/forum?utm_source=dev_to_client_server"&gt;Community Forum&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Introduction to Client-Server Architecture
&lt;/h1&gt;

&lt;p&gt;When you write code in an Anvil web app, there are two different places it can run: &lt;a href="https://anvil.works/beta-docs/client?utm_source=dev_to_client_server"&gt;the client&lt;/a&gt; or &lt;a href="https://anvil.works/beta-docs/server?utm_source=dev_to_client_server"&gt;the server&lt;/a&gt;. &lt;strong&gt;Client code runs on the user's browser&lt;/strong&gt; while &lt;strong&gt;server code runs on a central computer that you control.&lt;/strong&gt; If you are new to web development, the distinction between client and server can be confusing at first.  When you use Anvil, you don't need to worry about the messiness of traditional web development, but you still need to separate out where your code runs. &lt;/p&gt;

&lt;p&gt;The purpose of this article is to provide an introduction to the &lt;strong&gt;client-server architecture&lt;/strong&gt; and to explain why we need to write code for Anvil apps in two different places. We'll then use the &lt;a href="https://anvil.works/learn/tutorials/feedback-form?utm_source=dev_to_client_server"&gt;Feedback Form Tutorial&lt;/a&gt; as an example to better understand why we need to use both client-side and server-side code.&lt;/p&gt;

&lt;p&gt;In this article, we will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The client and client-side code&lt;/li&gt;
&lt;li&gt;The server and server-side code&lt;/li&gt;
&lt;li&gt;Communication between the client and server in Anvil&lt;/li&gt;
&lt;li&gt;The Feedback Form app as an example&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Client
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The front-end
&lt;/h3&gt;

&lt;p&gt;Client-side code runs in the user's browser and is responsible for showing the visual components of an app as well as interacting with the user. This code running on the client makes up the &lt;strong&gt;front-end&lt;/strong&gt; of a web app. In Anvil, your app's front-end is made up of &lt;a href="https://anvil.works/beta-docs/client/components/forms?utm_source=dev_to_client_server"&gt;Forms&lt;/a&gt; where you can drag and drop Python components to &lt;a href="https://anvil.works/beta-docs/client/ui?utm_source=dev_to_client_server"&gt;build a user interface&lt;/a&gt; and write client-side Python code.&lt;/p&gt;

&lt;p&gt;When a user opens a web app, all of the front-end code is downloaded to the browser, which means that the user has full control over that code. Because of this, &lt;strong&gt;client code is untrusted&lt;/strong&gt;. This is the reason we want some code to run elsewhere on a server. We don’t want the user to have access to code that authenticates users or processes payments, for example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-side Python
&lt;/h3&gt;

&lt;p&gt;In traditional (non-Anvil) web apps, front-end code is written in HTML and JavaScript because those are the languages that browsers can understand. Anvil lets you write all your front-end code in Python. This works because Anvil translates the Python code in your client-side Forms and Modules into JavaScript for the browser to execute. Not every Python library can be translated, which means that there are certain Python libraries that &lt;a href="https://anvil.works/docs/client/python#limits-to-python-in-the-browser?utm_source=dev_to_client_server"&gt;cannot be used on the client-side in Anvil&lt;/a&gt;. Server-side Python does not run in the browser, so there is no need for it to be translated into JavaScript. This is why extra Python libraries can be &lt;a href="https://anvil.works/docs/server/packages?utm_source=dev_to_client_server"&gt;used in server code&lt;/a&gt; or via &lt;a href="https://anvil.works/beta-docs/uplink?utm_source=dev_to_client_server"&gt;the Uplink&lt;/a&gt;. &lt;em&gt;If you're interested, Anvil uses the &lt;a href="http://skulpt.org/"&gt;Skulpt&lt;/a&gt; Python-to-JavaScript compiler. It's an open-source project, and &lt;a href="https://talkpython.fm/episodes/show/235/python-in-your-browser-with-skulpt"&gt;we contribute heavily to it&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Server-side code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The back-end
&lt;/h3&gt;

&lt;p&gt;Unlike client code, which runs in the user's browser, server-side code runs on a computer that is separate from the user’s device. This is the &lt;strong&gt;back-end&lt;/strong&gt; of a web app. Code written in an Anvil app's &lt;a href="https://anvil.works/beta-docs/server?utm_source=dev_to_client_server"&gt;Server Module&lt;/a&gt; will never be seen by your app's users. This means that it can act as a gatekeeper for central resources (such as data in your app's &lt;a href="https://anvil.works/beta-docs/data-tables?utm_source=dev_to_client_server"&gt;Data Tables&lt;/a&gt;) and enforce permissions checks (for example, checking if a user is an admin before showing information). &lt;/p&gt;

&lt;p&gt;&lt;em&gt;Learn more about &lt;a href="https://anvil.works/docs/security?utm_source=dev_to_client_server"&gt;building secure apps&lt;/a&gt; in our documentation.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In Anvil, we provide the server to run your server-side code, so you don’t have to worry about where to host it. In your Anvil apps, you just write code in a Server Module, and we provide the computer where it executes. This is called &lt;strong&gt;the Cloud&lt;/strong&gt;, or &lt;strong&gt;serverless computing&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Uplink
&lt;/h3&gt;

&lt;p&gt;However, your server-side code doesn't need to live on Anvil's servers. If you want to run your server-side code locally or on a different server, you can use the &lt;a href="https://anvil.works/beta-docs/uplink?utm_source=dev_to_client_server"&gt;Anvil Uplink&lt;/a&gt;. The Uplink is a way to connect an Anvil app to Python code running on a script somewhere else. For example, you could &lt;a href="https://anvil.works/learn/tutorials/google-colab-to-web-app?utm_source=dev_to_client_server"&gt;connect a script running in a Google Colab notebook&lt;/a&gt; to your web app via the Uplink. In this case, the &lt;a href="https://colab.research.google.com/"&gt;Google Colab&lt;/a&gt; notebook would be your app's back-end, and Google would be providing the server.&lt;/p&gt;

&lt;p&gt;You can also use your own computer as the server by hosting your code locally and connecting it to your app with the Uplink. However, once you shut your computer off or set it to sleep, the Uplink will disconnect and the script will no longer be running. To keep the script running so that your app continues to work properly, you can host your Uplink scripts on a virtual private server such as &lt;a href="https://aws.amazon.com/lightsail/"&gt;AWS Lightsail&lt;/a&gt; or &lt;a href="https://www.digitalocean.com/products/droplets"&gt;Digital Ocean Droplets&lt;/a&gt;. For more information on hosting your Uplinks scripts on an external server, see &lt;a href="https://anvil.works/learn/tutorials/google-colab-web-service?utm_source=dev_to_client_server"&gt;our guide on using Docker&lt;/a&gt; to containerise and run your scripts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Communicating between the client and server
&lt;/h2&gt;

&lt;p&gt;In Anvil, your app communicates by calling server-side functions from client-side code. You can make a server function available to the client using the &lt;a href="https://anvil.works/docs/server#calling-server-functions-from-client-code?utm_source=dev_to_client_server"&gt;&lt;code&gt;@anvil.server.callable&lt;/code&gt;&lt;/a&gt; decorator, and then call it from client code using &lt;code&gt;anvil.server.call&lt;/code&gt;. We'll see an example of this in the next section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sPzOkY4a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clx9jk5aghj13tcq771g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sPzOkY4a--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/clx9jk5aghj13tcq771g.png" alt="Client-server architecture" width="880" height="434"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;h2&gt;
  
  
  The Feedback Form: Putting it all together
&lt;/h2&gt;

&lt;p&gt;Let's take the &lt;a href="https://anvil.works/learn/tutorials/feedback-form?utm_source=dev_to_client_server"&gt;Feedback Form Tutorial&lt;/a&gt; as an example of a simple web app that uses the client-server architecture. The app has a simple user interface for taking user input and storing it in a Data Table. &lt;/p&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--dFgufxTJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qukkhz1cnijvg5h75xad.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--dFgufxTJ--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qukkhz1cnijvg5h75xad.gif" alt="Submitting feedback in the[Feedback Form Tutorial app](https://anvil.works/learn/tutorials/feedback-form?utm_source=dev_to_client_server)" width="880" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Client-side
&lt;/h3&gt;

&lt;p&gt;The front-end of the app is the user interface and the client-side code that collects user input. This code lives on the client because the user is interacting with it. After the user enters input and clicks the "submit" button, we use &lt;code&gt;anvil.server.call()&lt;/code&gt; to call a server function to add this input to the Data Table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_button_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="n"&gt;feedback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feedback_box&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="c1"&gt;# Call the 'add_feedback' server function
&lt;/span&gt;    &lt;span class="c1"&gt;# pass in name, email and feedback as arguments
&lt;/span&gt;    &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'add_feedback'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Server-side
&lt;/h3&gt;

&lt;p&gt;The server function and the Data Table are the back-end of our app. Because the server is separate from the user’s device, we can use it to run code we don’t want to expose to the user. In the case of the Feedback Form, we don't want users to have access to code that modifies the Data Table since it contains other users' data. By default, only server-side code has &lt;a href="https://anvil.works/beta-docs/data-tables/data-security?utm_source=dev_to_client_server"&gt;permission to access a Data Table&lt;/a&gt;. We can pass the user input to a server function that then updates the Data Table:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;callable&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_feedback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;app_tables&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;feedback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we looked at why we need to write code in two different places in Anvil apps. We covered why we write code on both the client and server, and how to communicate between the two in Anvil. Finally, we saw how the client-server architecture works in the Feedback Form Tutorial app. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>beginners</category>
      <category>python</category>
    </item>
    <item>
      <title>Draw FusionCharts with Python</title>
      <dc:creator>Brooke Myers</dc:creator>
      <pubDate>Thu, 10 Mar 2022 16:39:35 +0000</pubDate>
      <link>https://dev.to/bcm628/draw-fusioncharts-with-python-kc1</link>
      <guid>https://dev.to/bcm628/draw-fusioncharts-with-python-kc1</guid>
      <description>&lt;h1&gt;
  
  
  Draw beautiful charts with FusionCharts and Anvil
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://www.fusioncharts.com/"&gt;FusionCharts&lt;/a&gt; is a library for building beautiful dashboards in the browser, and with &lt;a href="https://anvil.works/?utm_source=dev_to_fusion_charts"&gt;Anvil&lt;/a&gt; you can build your FusionCharts dashboard entirely in Python, no HTML or JS required! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y8kviG-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28jv69t3jgnoc04tpfqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y8kviG-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28jv69t3jgnoc04tpfqf.png" alt="The chart we're going to create" width="747" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's jump in and build &lt;a href="https://www.fusioncharts.com/jquery-charts?framework=jquery"&gt;the standard FusionCharts demo plot&lt;/a&gt; in Anvil. Here's the plan:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create an Anvil app&lt;/li&gt;
&lt;li&gt;Add the FusionCharts library&lt;/li&gt;
&lt;li&gt;Create a FusionChart plot in Python&lt;/li&gt;
&lt;li&gt;Bonus: Add an event handler to the plot&lt;/li&gt;
&lt;li&gt;Deploy your app to the web&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  1. Create an Anvil app
&lt;/h2&gt;

&lt;p&gt;We're going to start with a blank app, built from Anvil's Material Design theme:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--X9TB9qzR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52gvbz43i54lucrr51sh.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--X9TB9qzR--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/52gvbz43i54lucrr51sh.gif" alt="Create a new Anvil app." width="880" height="696"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Add the FusionCharts library
&lt;/h2&gt;

&lt;p&gt;FusionCharts is distributed as a Javascript library, so we'll add that to our app in the &lt;a href="https://anvil.works/docs/client/javascript#using-native-javascript-libraries/?utm_source=dev_to_fusion_charts"&gt;Native Libraries&lt;/a&gt; section:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.fusioncharts.com/fusioncharts/latest/fusioncharts.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"text/javascript"&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.fusioncharts.com/fusioncharts/latest/themes/fusioncharts.theme.fusion.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all the HTML we have to write - the rest of our app will be written &lt;strong&gt;entirely from Python&lt;/strong&gt;!&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Create a FusionCharts plot in Python
&lt;/h2&gt;

&lt;p&gt;The blank app we created already has a form (&lt;code&gt;Form1&lt;/code&gt;) that will load when someone visits our app, so let's add a placeholder component to hold the plot. A &lt;code&gt;Spacer&lt;/code&gt; works nicely. Drag and drop one from the Toolbox onto the form, set the height to something suitable, and rename it to &lt;code&gt;chart_placeholder&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--bf41WlD2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh4aoy07dnbbrwartc4m.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--bf41WlD2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zh4aoy07dnbbrwartc4m.gif" alt="Create a placeholder for your chart." width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We want to display the chart when the form is opened, so select the form and then scroll down to the bottom of the Properties panel to create an event handler for the &lt;code&gt;show&lt;/code&gt; event:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8p5jXYqq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hh2xvcrwr4r2ic2hjppo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8p5jXYqq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hh2xvcrwr4r2ic2hjppo.gif" alt="Create an event handler for the form ‘show’ event." width="880" height="495"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anvil has created the &lt;code&gt;form_show&lt;/code&gt; event handler for you, and this is where we want to write code to display our chart. Before we get to that, let's import the things we need. We'll be using the &lt;a href="https://anvil.works/docs/client/javascript#accessing-javascript-from-python/?utm_source=dev_to_fusion_charts"&gt;&lt;code&gt;anvil.js&lt;/code&gt;&lt;/a&gt; module to interact with the FusionCharts Javascript, so start by importing that at the top of the file. At the same time, let's add the data we want to plot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;anvil&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;anvil.js&lt;/span&gt; &lt;span class="c1"&gt;# Add this import
&lt;/span&gt;
&lt;span class="n"&gt;chart_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Venezuela"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"290"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Saudi"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"260"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Canada"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"180"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Iran"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"140"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Russia"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"115"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"UAE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"100"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"US"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"30"&lt;/span&gt;
  &lt;span class="p"&gt;},{&lt;/span&gt;
    &lt;span class="s"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"China"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"30"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c1"&gt;# 
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Form1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Form1Template&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now update your new &lt;code&gt;form_show&lt;/code&gt; event handler to create the chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""This method is called when the form is shown on the screen"""&lt;/span&gt;

    &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FusionCharts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="s"&gt;"renderAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_dom_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chart_placeholder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"column2d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"dataSource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"chart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s"&gt;"caption"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Countries With Most Oil Reserves [2017-18]"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"subCaption"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"In MMbbl = One Million barrels"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"xAxisName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Country"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"yAxisName"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Reserves (MMbbl)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"numberSuffix"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"K"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"theme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"fusion"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="s"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chart_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;chart&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that's it! Hit "Run" to see your chart. It should look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--y8kviG-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28jv69t3jgnoc04tpfqf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--y8kviG-G--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/28jv69t3jgnoc04tpfqf.png" alt="Image description" width="747" height="422"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Bonus step: Add an event handler to your plot
&lt;/h2&gt;

&lt;p&gt;FusionCharts are fully interactive, and we can easily wire up their events to Python methods in our app. Let's pop up an &lt;a href="https://anvil.works/docs/client/python/alerts-and-notifications#messages"&gt;alert&lt;/a&gt; when the user clicks one of the bars on the plot.&lt;/p&gt;

&lt;p&gt;First, we'll add a method to our form to handle the event:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;data_plot_click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"You clicked on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;categoryLabel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, which has a value of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;displayValue&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we need to modify our chart generation code to pass the method to FusionCharts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;event_args&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""This method is called when the form is shown on the screen"""&lt;/span&gt;

    &lt;span class="n"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FusionCharts&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="s"&gt;"renderAt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;anvil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;js&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_dom_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;spacer_1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"column2d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="c1"&gt;## Add this section to register our new event handler
&lt;/span&gt;      &lt;span class="s"&gt;"events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s"&gt;"dataplotclick"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data_plot_click&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="s"&gt;"height"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"100%"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all we need. Run your app again, and try clicking on one of the bars to see the popup.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Deploy your app to the web
&lt;/h2&gt;

&lt;p&gt;Anvil makes it really easy to publish your new FusionCharts dashboard to the web. Deploying the app is as simple as clicking 'Publish App' in the app's settings and choosing a URL:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--GMK4RRvg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzh52vgew85bth5sm9va.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--GMK4RRvg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qzh52vgew85bth5sm9va.gif" alt="Choosing a URL and deploying our new app to it." width="880" height="597"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now our app is available to anyone on the web!&lt;/p&gt;




&lt;h2&gt;
  
  
  That's it!
&lt;/h2&gt;

&lt;p&gt;We've just created a web app with nothing but Python, even though the FusionCharts library usually requires Javascript!&lt;/p&gt;

&lt;p&gt;Check out the full source code of the app &lt;a href="https://anvil.works/build#clone:M57NM476LA3GA3AE%3d2ONFMHO2J3QUYIJFFD7OYZEF"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More about Anvil
&lt;/h2&gt;

&lt;p&gt;If you're new here, welcome! &lt;a href="https://anvil.works/?utm_source=dev_to_fusion_charts"&gt;Anvil&lt;/a&gt; is a platform for building full-stack web apps with nothing but Python. No need to wrestle with JS, HTML, CSS, Python, SQL and all their frameworks – just &lt;strong&gt;build it all in Python&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
