<?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>Forem: Tarek Oraby</title>
    <description>The latest articles on Forem by Tarek Oraby (@tarekoraby).</description>
    <link>https://forem.com/tarekoraby</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%2F805231%2F85841e5c-4092-4d60-8c8f-9e282b6af5dc.jpg</url>
      <title>Forem: Tarek Oraby</title>
      <link>https://forem.com/tarekoraby</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tarekoraby"/>
    <language>en</language>
    <item>
      <title>End-to-end testing of Gen AI Apps</title>
      <dc:creator>Tarek Oraby</dc:creator>
      <pubDate>Sat, 11 Oct 2025 12:09:39 +0000</pubDate>
      <link>https://forem.com/tarekoraby/end-to-end-testing-of-gen-ai-apps-2j7j</link>
      <guid>https://forem.com/tarekoraby/end-to-end-testing-of-gen-ai-apps-2j7j</guid>
      <description>&lt;p&gt;The shift to building applications on top of Large Language Models (LLMs) fundamentally breaks traditional software testing. Methodologies designed for predictable, deterministic systems are ill-equipped to handle the non-deterministic nature of LLM-based apps, creating a critical gap in quality assurance. This blog post dives into the specific challenges of end-to-end testing for these apps and introduces an open-source framework, SigmaEval, designed to provide a statistically rigorous solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Challenges of Testing Gen AI Apps
&lt;/h2&gt;

&lt;p&gt;Testing Gen AI applications is fundamentally different from testing traditional software. The core difficulty stems from two interconnected problems that create a cascade of complexity.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Infinite Input Space:&lt;/strong&gt; The range of possible user inputs is basically endless. You cannot write enough static test cases to cover every scenario or every subtle variation in how a user might phrase a question. This problem is compounded in conversational AI, where multi-turn chats branch out fast. Even small wording changes in an early turn can dramatically shift how later turns unfold, making the state space of a conversation explode.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Non-Deterministic &amp;amp; "Fuzzy" Outputs:&lt;/strong&gt; Unlike a traditional function where &lt;code&gt;2 + 2&lt;/code&gt; is always &lt;code&gt;4&lt;/code&gt;, a Gen AI model can produce a wide variety of responses to the same prompt. Outputs aren't consistent, so a single clean run doesn’t mean much. On top of this, quality itself is fuzzy. There’s rarely a single right answer, and you’re often juggling competing priorities like helpfulness, safety, and tone all at once. Even human judges don’t always agree on what constitutes a "good" response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two fundamental challenges are amplified by the complex systems we build around the models. The integration of tools, memory, and RAG adds more moving parts, each a potential point of failure. External APIs or vector indexes drift over time, and even the underlying models can change quietly in the background. When you add operational concerns like costs, rate limits, and flaky integrations, it’s easy to think you’ve tested enough when you really haven’t.&lt;/p&gt;

&lt;p&gt;This reality means we must move beyond simple pass/fail checks and adopt a more robust, statistical approach to evaluation. The process becomes less like traditional software testing and more like a clinical trial. The goal isn't to guarantee a specific outcome for every individual interaction, but to ensure the application is effective for a significant portion of users, within a defined risk tolerance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing SigmaEval: A Statistical Approach to Gen AI Evaluation
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/SigmaEval/SigmaEval" rel="noopener noreferrer"&gt;SigmaEval&lt;/a&gt; is an open-source Python framework designed specifically for the statistical evaluation of Gen AI apps, agents, and bots. It helps you move from "it seems to work" to making statistically rigorous statements about your AI's quality.&lt;/p&gt;

&lt;p&gt;With SigmaEval, you can set and enforce objective quality bars, making statements like:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"We are confident that at least 90% of user issues coming into our customer support chatbot will be resolved with a quality score of 8/10 or higher."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;SigmaEval addresses the challenges of Gen AI testing through a novel approach that combines AI-driven user simulation, an AI judge, and inferential statistics.&lt;/p&gt;

&lt;h3&gt;
  
  
  How SigmaEval Works
&lt;/h3&gt;

&lt;p&gt;SigmaEval uses two AI agents to automate the evaluation process: an &lt;strong&gt;AI User Simulator&lt;/strong&gt; and an &lt;strong&gt;AI Judge&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Define "Good":&lt;/strong&gt; You start by defining a test scenario in plain language, describing the user's goal and the successful outcome you expect. This becomes your objective quality bar.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Simulate and Collect Data:&lt;/strong&gt; The AI User Simulator acts as a test user, interacting with your application based on your scenario. It runs these interactions multiple times to collect a robust dataset.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Judge and Analyze:&lt;/strong&gt; The AI Judge scores each interaction against your definition of success. SigmaEval then applies statistical methods to these scores to determine if your quality bar has been met with a specified level of confidence.&lt;/li&gt;
&lt;/ol&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%2Fhh2bjiqt1472ciot5veb.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%2Fhh2bjiqt1472ciot5veb.png" alt="How SigmaEval works" width="800" height="1259"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This process transforms subjective assessments into quantitative, data-driven conclusions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting Started with SigmaEval
&lt;/h3&gt;

&lt;p&gt;Here’s a simple example of how to use SigmaEval to test a customer support chatbot's refund process:&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;sigmaeval&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SigmaEval&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ScenarioTest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;assertions&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Define a realistic, multi-turn ScenarioTest
&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;ScenarioTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Customer Support Refund Process&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A user is interacting with a customer support chatbot for an &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;e-commerce store called &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GadgetWorld&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;when&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The user wants to get a refund for a faulty product they received.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expect_behavior&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The chatbot should first empathize with the user&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s situation, then ask for &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the order number, and finally explain the next steps for the refund process. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;It should not resolve the issue immediately but guide the user effectively.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# We want to be confident that at least 90% of interactions will score an 8/10 or higher.
&lt;/span&gt;        &lt;span class="n"&gt;criteria&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;assertions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scores&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;proportion_gte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_score&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proportion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.9&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="nf"&gt;max_turns&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="c1"&gt;# Allow for a longer, more complex back-and-forth conversation
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Implement the app_handler to connect SigmaEval to your application
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;app_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# This handler is the bridge between SigmaEval and your application.
&lt;/span&gt;    &lt;span class="c1"&gt;# It's responsible for taking the conversation history from the AI User Simulator
&lt;/span&gt;    &lt;span class="c1"&gt;# and getting a response from your app, enabling multi-turn conversation testing.
&lt;/span&gt;
    &lt;span class="c1"&gt;# `messages` contains the full conversation history, e.g.:
&lt;/span&gt;    &lt;span class="c1"&gt;# [
&lt;/span&gt;    &lt;span class="c1"&gt;#   {'role': 'user', 'content': 'My new headphones are broken.'},
&lt;/span&gt;    &lt;span class="c1"&gt;#   {'role': 'assistant', 'content': 'I am sorry to hear that. What is your order number?'}
&lt;/span&gt;    &lt;span class="c1"&gt;#   {'role': 'user', 'content': 'It's GADGET-12345.'}
&lt;/span&gt;    &lt;span class="c1"&gt;# ]
&lt;/span&gt;
    &lt;span class="c1"&gt;# In a real test, you would make an API call to your application's endpoint here.
&lt;/span&gt;    &lt;span class="c1"&gt;# For example:
&lt;/span&gt;    &lt;span class="c1"&gt;# response = await http_client.post(
&lt;/span&gt;    &lt;span class="c1"&gt;#     "https://your-chatbot-api.com/chat",
&lt;/span&gt;    &lt;span class="c1"&gt;#     json={"messages": messages, "session_id": state}
&lt;/span&gt;    &lt;span class="c1"&gt;# )
&lt;/span&gt;    &lt;span class="c1"&gt;# return response.json()["content"]
&lt;/span&gt;
    &lt;span class="c1"&gt;# This part must be implemented to return the actual response from your app.
&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Connect this handler to your application.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# 3. Initialize SigmaEval and run the evaluation
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;sigma_eval&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SigmaEval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;judge_model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini/gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sample_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# The number of times to run the test
&lt;/span&gt;        &lt;span class="n"&gt;significance_level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;  &lt;span class="c1"&gt;# Corresponds to a 95% confidence level
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;sigma_eval&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scenario&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app_handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Assert that the test passed for integration with testing frameworks like pytest
&lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;passed&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__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;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# To run this, you would need to implement the app_handler
&lt;/span&gt;    &lt;span class="c1"&gt;# and handle the potential NotImplementedError.
&lt;/span&gt;    &lt;span class="c1"&gt;# asyncio.run(main())
&lt;/span&gt;    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this code is executed (after implementing the &lt;code&gt;app_handler&lt;/code&gt;), SigmaEval automates the entire end-to-end testing process:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Rubric Generation:&lt;/strong&gt; The AI Judge first interprets the &lt;code&gt;.expect_behavior()&lt;/code&gt; description to generate a detailed 1-10 scoring rubric that defines success for this specific scenario.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;User Simulation:&lt;/strong&gt; The AI User Simulator then runs 20 (&lt;code&gt;sample_size&lt;/code&gt;) independent, multi-turn conversations with your application via the &lt;code&gt;app_handler&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;AI-Powered Judging:&lt;/strong&gt; Each of the 20 conversation transcripts is scored against the rubric by the AI Judge.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Statistical Conclusion:&lt;/strong&gt; SigmaEval performs a hypothesis test on the collected scores.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The final output is a &lt;code&gt;result&lt;/code&gt; object. Its &lt;code&gt;result.passed&lt;/code&gt; attribute will be &lt;code&gt;True&lt;/code&gt; only if the test can conclude, with 95% confidence, that your chatbot meets the quality bar: at least 90% of interactions will score an 8 or higher. The &lt;code&gt;result&lt;/code&gt; object also contains a wealth of data for inspection, including the full conversation logs, individual scores, the generated rubric, and the statistical analysis summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Concepts
&lt;/h3&gt;

&lt;p&gt;SigmaEval uses a fluent builder API with a &lt;strong&gt;Given-When-Then&lt;/strong&gt; pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;.given()&lt;/code&gt;: Establishes the context for the AI User Simulator.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;.when()&lt;/code&gt;: Describes the goal or action the user will take.&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;.expect_behavior()&lt;/code&gt; or &lt;code&gt;.expect_metric()&lt;/code&gt;: Specifies the expected outcomes, with statistical criteria.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Features
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Statistical Assertions:&lt;/strong&gt; Go beyond pass/fail with assertions like &lt;code&gt;proportion_gte&lt;/code&gt; (is the performance good enough, most of the time?) and &lt;code&gt;median_gte&lt;/code&gt; (is the typical user experience good?).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;User Simulation with Various Writing Styles:&lt;/strong&gt; The AI User Simulator can adopt different writing styles to ensure your app is robust to real-world user communication.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Built-in Metrics:&lt;/strong&gt; Measure quantitative aspects like response latency and turn count out of the box.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Compatibility with Testing Libraries:&lt;/strong&gt; Integrates seamlessly with &lt;code&gt;pytest&lt;/code&gt; and &lt;code&gt;unittest&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Practical Applications
&lt;/h3&gt;

&lt;p&gt;SigmaEval is designed to integrate into a standard engineering workflow, providing a structured approach to Gen AI quality assurance. Its primary applications are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;CI/CD Integration:&lt;/strong&gt; The framework's programmatic interface and clear pass/fail criteria (based on statistical confidence) allow it to be used as a quality gate in CI/CD pipelines. This prevents regressions in application behavior that might otherwise go unnoticed.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Objective A/B Testing:&lt;/strong&gt; When comparing two versions of a prompt, an agent, or an underlying model, SigmaEval can provide statistically significant results on which version performs better against a defined quality bar.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Regression Testing:&lt;/strong&gt; By running a suite of &lt;code&gt;ScenarioTest&lt;/code&gt;s, you can create a regression suite to ensure that changes to one part of your system don't negatively impact the holistic user experience.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;p&gt;SigmaEval is an open-source project. The repository contains the full source code and documentation: &lt;a href="https://github.com/SigmaEval/SigmaEval" rel="noopener noreferrer"&gt;https://github.com/SigmaEval/SigmaEval&lt;/a&gt;. A dedicated documentation site is also available at &lt;a href="https://docs.sigmaeval.com/" rel="noopener noreferrer"&gt;docs.sigmaeval.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From Code to Cash: Monetizing Python AI Agents ⚡</title>
      <dc:creator>Tarek Oraby</dc:creator>
      <pubDate>Fri, 09 May 2025 15:15:02 +0000</pubDate>
      <link>https://forem.com/tarekoraby/from-code-to-cash-monetizing-python-ai-agents-4bgc</link>
      <guid>https://forem.com/tarekoraby/from-code-to-cash-monetizing-python-ai-agents-4bgc</guid>
      <description>&lt;p&gt;So, you've poured your heart into crafting the perfect AI agent. It runs flawlessly on your machine, and you know it solves a real market need. But what's next? Bridging the gap between a locally successful agent and a revenue-generating product is a journey fraught with challenges. Building the agent was just the first step. Now, the real work begins: convincing clients, navigating complex deployments, ensuring reliability, and, ultimately, getting paid for your hard work.&lt;/p&gt;

&lt;p&gt;This post is your practical guide to transforming your AI agent into a profitable venture. By the end, you'll understand how to leverage Stripe, GitHub, and other tools to turn your AI creation into a product that generates a steady stream of revenue. Specifically, we will cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Implementing outcome-based pricing for your agent.&lt;/li&gt;
&lt;li&gt;Deploying your agent as an API service.&lt;/li&gt;
&lt;li&gt;Managing customer payments and subscriptions (using &lt;strong&gt;Stripe&lt;/strong&gt;, &lt;strong&gt;GitHub&lt;/strong&gt;, and other tools).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Embrace Outcome-Based Pricing?
&lt;/h2&gt;

&lt;p&gt;Outcome-based pricing is a model where customers pay based on the tangible results they achieve. It's an increasingly popular strategy for AI agents because it perfectly aligns your interests as the provider with those of your customer. Clients pay only when they receive measurable business value, and you earn only when your agent delivers that value. With an agreed-upon unit price for each resolved ticket, secured appointment, or completed transaction, both you and your customer can instantly quantify the return on investment (ROI).&lt;/p&gt;

&lt;p&gt;Growing evidence suggests that outcome-based pricing can lead to shorter sales cycles and higher conversion rates. This is likely because it removes the uncertainty surrounding your agent's value proposition, making it an easier "yes" for potential customers. Furthermore, it reassures clients of your commitment to ongoing maintenance and improvement, as they know your revenue stream depends on the agent's continued performance.&lt;/p&gt;

&lt;p&gt;Compared to bespoke development projects, fixed-price contracts, or traditional subscription models, outcome-based pricing offers greater transparency and fairness. The primary challenge lies in its technical implementation, which requires precise metering, attribution, and analytics. In this post, we'll demonstrate how to implement this pricing model using Stripe and integrate it with a deployment platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example: A Lead Enrichment Agent
&lt;/h2&gt;

&lt;p&gt;To illustrate the process, let's consider an AI agent designed to monitor a Google Sheet of incoming leads and automatically enrich each one with additional information sourced from the web. (You can find a reference implementation of this agent in this &lt;a href="https://github.com/Itura-AI/lead-enrichment-agent" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;). At a high level, the agent functions as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; A Google &lt;a href="https://developers.google.com/apps-script" rel="noopener noreferrer"&gt;Apps Script&lt;/a&gt; Web app monitors the Google Sheet where leads are stored.&lt;/li&gt;
&lt;li&gt; When a new lead is added, the Apps Script sends a POST request containing the lead's email to the agent's API service (which we will set up).&lt;/li&gt;
&lt;li&gt; The agent, a Python script utilizing Gemini's &lt;a href="https://ai.google.dev/gemini-api/docs/grounding?lang=python" rel="noopener noreferrer"&gt;Grounding with Google Search&lt;/a&gt; and structured outputs, scours the web for information about the lead and their company. It then sends a response back to the Apps Script's web app URL.&lt;/li&gt;
&lt;li&gt; The Apps Script updates the Google Sheet with the enriched lead data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's a simplified snippet illustrating the agent's core functionality (the complete code is available in the &lt;a href="https://github.com/Itura-AI/lead-enrichment-agent" rel="noopener noreferrer"&gt;GitHub repository&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_and_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&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;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rowData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Email&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Simplified error handling for brevity
&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;status&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;Error: Email not found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

        &lt;span class="n"&gt;enriched_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lead_enrichment_agent&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;processed_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enriched_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;
        &lt;span class="n"&gt;callback_status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;enriched_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProcessingStatus&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Completed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="n"&gt;callback_payload&lt;/span&gt; &lt;span class="o"&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;callbackInfo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;dataclasses&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;callbackInfo&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;processedData&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;processed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;callback_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;callback_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CALLBACK_URL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Assume CALLBACK_URL is always set for this simplified version
&lt;/span&gt;        &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_url&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="n"&gt;callback_payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&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;status&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;success&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Simplified error callback for brevity
&lt;/span&gt;        &lt;span class="n"&gt;error_payload&lt;/span&gt; &lt;span class="o"&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;status&lt;/span&gt;&lt;span class="sh"&gt;"&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="s"&gt;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="c1"&gt;# Attempt to send minimal error info
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;callback_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# Check if callback_url was retrieved
&lt;/span&gt;             &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;callback_url&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="n"&gt;error_payload&lt;/span&gt;&lt;span class="p"&gt;)&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;status&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;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, imagine you've successfully demoed this agent to a potential customer. They're convinced it's a perfect fit for their needs and are comfortable with the outcome-based pricing model. To deploy the agent for them, we need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a Stripe subscription for the customer.&lt;/li&gt;
&lt;li&gt;Package the agent code to accept API requests.&lt;/li&gt;
&lt;li&gt;Deploy the agent as an API service.&lt;/li&gt;
&lt;li&gt;Enable the agent to be triggered and ensure the customer is billed for usage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's delve into each of these steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Setting Up Stripe
&lt;/h2&gt;

&lt;p&gt;First, you'll need a Stripe account. If you don't have one, you can create it &lt;a href="https://dashboard.stripe.com/register" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Once your account is active, navigate to the Stripe dashboard and find the "Products" section:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Click &lt;strong&gt;"Create Product"&lt;/strong&gt;. Provide a name (e.g., "Lead Enrichment Agent") and an optional description.&lt;/li&gt;
&lt;li&gt; In the pricing section, click &lt;strong&gt;"More pricing options"&lt;/strong&gt;. Ensure "Recurring" is selected, then choose &lt;strong&gt;"Usage-based pricing"&lt;/strong&gt; and &lt;strong&gt;"Per unit"&lt;/strong&gt;. Set a price per unit for your product (e.g., $1.00 USD). This will be the price per successfully enriched lead.&lt;/li&gt;
&lt;li&gt; Under the "Usage" section, click the &lt;strong&gt;add button&lt;/strong&gt; next to the "Meter" field. This opens a dialog to define how usage is tracked and billed.

&lt;ul&gt;
&lt;li&gt;Give your meter a name (e.g., "Leads Enrichment Meter").&lt;/li&gt;
&lt;li&gt;Assign an event name (e.g., "leads_enrichment_meter").&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;"Create meter"&lt;/strong&gt; to save.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; With the new meter selected, you'll return to the pricing section. Click &lt;strong&gt;"Next"&lt;/strong&gt; to go back to the "Add a product" form.&lt;/li&gt;
&lt;li&gt; Finally, click &lt;strong&gt;"Add product"&lt;/strong&gt; to complete the product creation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now you have a product ready for billing. Next, add your client as a Stripe "Customer" and create a new subscription for them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to the &lt;strong&gt;"Customers"&lt;/strong&gt; section and click &lt;strong&gt;"Add customer"&lt;/strong&gt;. Fill in their details, such as name, email, and preferred billing information.&lt;/li&gt;
&lt;li&gt; Navigate to the &lt;strong&gt;"Subscriptions"&lt;/strong&gt; section and click &lt;strong&gt;"Create subscription"&lt;/strong&gt;.

&lt;ul&gt;
&lt;li&gt;In the form, select the customer you just created.&lt;/li&gt;
&lt;li&gt;Select the product you configured earlier.&lt;/li&gt;
&lt;li&gt;Save the subscription.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it! You've established the necessary Stripe product and customer setup to bill for your agent's services. While Stripe offers many advanced features, this covers the essentials to get you started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Preparing Your Agent for Deployment
&lt;/h2&gt;

&lt;p&gt;To deploy our agent as an API service, we'll use &lt;a href="https://itura.ai" rel="noopener noreferrer"&gt;Itura's deployment platform&lt;/a&gt;. Itura simplifies deploying your agent as an API, monitoring its execution, and integrating with GitHub for automatic deployments upon pull request merges. Crucially, Itura also integrates with Stripe to send meter events when your agent is triggered.&lt;/p&gt;

&lt;p&gt;Preparing your agent for Itura deployment is straightforward: wrap its core logic in a Flask endpoint. This endpoint will receive data from the customer's webhook and trigger the agent. Upon successful execution, the endpoint sends a callback to the customer's webhook with the results. Itura handles updating the Stripe meter with the consumed units.&lt;/p&gt;

&lt;p&gt;For our Lead Enrichment Agent, the Flask endpoint will look like this:&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;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;process_and_callback&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RequestData&lt;/span&gt; &lt;span class="c1"&gt;# Assuming RequestData is defined elsewhere
&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&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="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/run&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract data using type-safe models.
&lt;/span&gt;    &lt;span class="c1"&gt;# Ensure RequestData.from_dict() is implemented to parse the JSON payload
&lt;/span&gt;    &lt;span class="n"&gt;request_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_dict&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;process_and_callback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_data&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 the core change! Simply wrap your agent's logic in a Flask endpoint and define a &lt;code&gt;RequestData&lt;/code&gt; model (or similar mechanism) to parse incoming data. Additionally, ensure you have a &lt;code&gt;requirements.txt&lt;/code&gt; file specifying the agent's dependencies (easily generated by running &lt;code&gt;pip freeze &amp;gt; requirements.txt&lt;/code&gt; in your project's virtual environment).&lt;/p&gt;

&lt;p&gt;With the Flask endpoint and &lt;code&gt;requirements.txt&lt;/code&gt; file in place, push your code to a GitHub repository. Now you're ready for deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Deploying the Agent as an API Service
&lt;/h2&gt;

&lt;p&gt;To deploy your agent, create an account at &lt;a href="https://app.itura.ai" rel="noopener noreferrer"&gt;Itura&lt;/a&gt;. During signup, you'll be prompted to connect your GitHub and Stripe accounts. Once connected, create a new agent deployment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Click the &lt;strong&gt;"New Agent"&lt;/strong&gt; button.&lt;/li&gt;
&lt;li&gt; Select the GitHub repository containing your agent's code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The deployment process will take a couple of minutes. Once complete, you'll receive the agent's URL, which will look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://{agentSlug}.agent.itura.ai/run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This URL is used to trigger your agent. However, before you can do that, you need to link the Stripe customer ID and subscription with the agent deployment in Itura. This enables Itura to automatically send meter events to Stripe when the agent is triggered and to invalidate the agent's API key if the subscription is canceled.&lt;/p&gt;

&lt;p&gt;In your Itura agent dashboard:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Click on &lt;strong&gt;"Customers"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Add the customer by looking up their account from your connected Stripe account.&lt;/li&gt;
&lt;li&gt; Select the subscription you created earlier for this customer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once the customer is added, you'll likely need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Create a new API key for this customer:&lt;/strong&gt; Navigate to the "API keys" view and select the customer you just added. Itura will generate a new API key for them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add necessary environment variables:&lt;/strong&gt; For the Lead Enrichment Agent, you'll need to add &lt;code&gt;CALLBACK_URL&lt;/code&gt; and &lt;code&gt;GEMINI_API_KEY&lt;/code&gt;. These environment variables (especially &lt;code&gt;CALLBACK_URL&lt;/code&gt;) should ideally be associated with a specific customer, allowing you to use the same agent deployment for multiple clients, each with their unique configurations.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Step 4: Triggering the Agent and Billing the Customer
&lt;/h2&gt;

&lt;p&gt;With everything set up, your agent is ready to be triggered using its unique URL and the customer-specific API key. For example, the following cURL command will trigger the agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer {customer-specific-api-key}"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"callbackInfo": {"rowNumber": 1, "spreadsheetId": "your_spreadsheet_id"}, "rowData": {"Email": "john.doe@example.com"}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://&lt;span class="o"&gt;{&lt;/span&gt;agentSlug&lt;span class="o"&gt;}&lt;/span&gt;.agent.itura.ai/run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, Itura will inspect the provided API key, identify the associated customer, and trigger the agent, injecting the customer's environment variables into the serverless execution environment. If the agent executes successfully, Itura sends an event to Stripe, reporting 1 unit of subscription consumption. The customer will then be billed accordingly at the end of their billing cycle. If needed, you can also customize your agent's logic to return a specific number of units consumed by including a "billedUnits" field in its response.&lt;/p&gt;

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

&lt;p&gt;Transforming a locally successful Python AI agent into a consistent revenue stream presents unique challenges, from articulating its value to managing deployments and ensuring ongoing reliability. However, by adopting an outcome-based pricing model and leveraging the right tools, these hurdles can be effectively overcome.&lt;/p&gt;

&lt;p&gt;This post has guided you through a practical approach to productizing your AI agent. We've explored how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Outcome-based pricing&lt;/strong&gt; aligns your incentives with your customer's, simplifying sales by focusing on tangible results and ROI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; can be configured to support this model, enabling you to meter usage and bill customers based on the value your agent delivers.&lt;/li&gt;
&lt;li&gt;Preparing your agent with a simple &lt;strong&gt;Flask API&lt;/strong&gt; makes it ready for scalable, cloud-based deployment.&lt;/li&gt;
&lt;li&gt;A platform like &lt;strong&gt;Itura&lt;/strong&gt; can drastically simplify deployment, customer management, and billing integration, allowing you to deploy, monitor, and manage customer-specific configurations and API keys with ease.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By following these steps, you can shift your focus from operational complexities to what you do best: building intelligent AI agents that solve real-world problems. The path from a clever script to a profitable product is now clearer, empowering you to turn your AI creations into sustainable and scalable businesses.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>solopreneur</category>
      <category>python</category>
    </item>
    <item>
      <title>Deploy CrewAI as an API service (in 10 min)⚡</title>
      <dc:creator>Tarek Oraby</dc:creator>
      <pubDate>Wed, 16 Apr 2025 12:46:57 +0000</pubDate>
      <link>https://forem.com/tarekoraby/deploy-crewai-agent-to-the-cloud-3aee</link>
      <guid>https://forem.com/tarekoraby/deploy-crewai-agent-to-the-cloud-3aee</guid>
      <description>&lt;p&gt;This guide walks you through the process of deploying a CrewAI agent to a cloud endpoint. We'll use &lt;a href="https://itura.ai" rel="noopener noreferrer"&gt;Itura's free tier&lt;/a&gt; to host the agent in a serverless environment. The deployment will be connected to a GitHub repository, so any changes to the code will automatically be reflected in the deployment.&lt;/p&gt;

&lt;p&gt;(This guide is also available in video format: &lt;a href="https://youtu.be/HFWlnIfXc_o" rel="noopener noreferrer"&gt;https://youtu.be/HFWlnIfXc_o&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;About 15 minutes&lt;/li&gt;
&lt;li&gt;A GitHub account&lt;/li&gt;
&lt;li&gt;A basic understanding of CrewAI&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;uv&lt;/code&gt; package manager (the default package manager for CrewAI)&lt;/li&gt;
&lt;li&gt;An OpenAI API key (or other LLM API key)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What you will deploy
&lt;/h2&gt;

&lt;p&gt;You will deploy a simple CrewAI agent to a serverless cloud endpoint. The endpoint will accept POST requests at a URL similar to &lt;code&gt;https://&amp;lt;your-agent-slug&amp;gt;.agent.itura.ai/run&lt;/code&gt;. When a POST request is sent to this URL, the agent will be initiated and will start executing. Environment variables can be added to the agent, which will be injected at runtime.&lt;/p&gt;

&lt;p&gt;Our starting point is a simple CrewAI crew consisting of two agents that work together to create a research report on the current state of LLMs. The key entry point for the crew is the &lt;code&gt;main.py&lt;/code&gt; file.&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="c1"&gt;#!/usr/bin/env python
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai_deployment_example.crew&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CrewaiDeploymentExample&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Run the crew.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&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;topic&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;AI LLMs&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;current_year&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;2025&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nc"&gt;CrewaiDeploymentExample&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Optionally, if you want, run the agent locally by adding the necessary entries to the &lt;code&gt;.env&lt;/code&gt; file. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MODEL=gpt-4o-mini
OPENAI_API_KEY=&amp;lt;your-openai-api-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then using the &lt;a href="https://docs.crewai.com/installation" rel="noopener noreferrer"&gt;CrewAI CLI&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crewai run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to complete this guide
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Download and unzip the &lt;a href="https://github.com/Itura-AI/crewai-deployment-example" rel="noopener noreferrer"&gt;source repository for this guide&lt;/a&gt;, or clone it using Git: &lt;code&gt;git clone https://github.com/Itura-AI/crewai-deployment-example.git&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cd&lt;/code&gt; into &lt;code&gt;crewai-deployment-example&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;When you finish, you can check your work against the code in the &lt;code&gt;crewai-deployment-example/complete&lt;/code&gt; branch.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying a CrewAI agent
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Install Flask
&lt;/h3&gt;

&lt;p&gt;To deploy the agent to the Itura cloud platform, we'll need to create a simple HTTP endpoint that will be used to initiate the agent process. We'll use Flask to create this endpoint. First, install Flask using the &lt;code&gt;uv&lt;/code&gt; package manager.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add flask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create a /run endpoint
&lt;/h3&gt;

&lt;p&gt;To initiate the agent process, Itura looks for a &lt;code&gt;/run&lt;/code&gt; endpoint that accepts POST requests. Let's add this endpoint to our agent code. Adjust the &lt;code&gt;main.py&lt;/code&gt; file to include the following:&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="c1"&gt;#!/usr/bin/env python
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai_deployment_example.crew&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CrewaiDeploymentExample&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&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="n"&gt;jsonify&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&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="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/run&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&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;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Run the crew.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;inputs&lt;/span&gt; &lt;span class="o"&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;topic&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;AI LLMs&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;current_year&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;2025&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# You can get the inputs from the request body
&lt;/span&gt;    &lt;span class="c1"&gt;# inputs = request.json
&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;CrewaiDeploymentExample&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;output&lt;/span&gt;&lt;span class="sh"&gt;"&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="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;jsonify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

&lt;span class="c1"&gt;## This is optional,
## but uncomment if you want to run the Flask server locally
# if __name__ == '__main__':
#   app.run(host='0.0.0.0', port=5000)
&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Create a requirements.txt file
&lt;/h3&gt;

&lt;p&gt;You need to create a &lt;code&gt;requirements.txt&lt;/code&gt; file to specify the dependencies for our agent. Itura will use this file to install the dependencies for our agent when it is deployed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv pip compile pyproject.toml &lt;span class="nt"&gt;-o&lt;/span&gt; requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have the Flask &lt;code&gt;run&lt;/code&gt; endpoint and the &lt;code&gt;requirements.txt&lt;/code&gt; file, &lt;strong&gt;push your code to a GitHub repository&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Deploy to Itura Cloud
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://app.itura.ai" rel="noopener noreferrer"&gt;app.itura.ai&lt;/a&gt; and create a new agent project. You'll be prompted to connect your GitHub account and select the repository and branch you want to deploy. Select the branch holding your agent code (with the Flask &lt;code&gt;run&lt;/code&gt; endpoint and &lt;code&gt;requirements.txt&lt;/code&gt; file), and click &lt;strong&gt;Deploy&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Note that the deployment might take a couple of minutes to complete.&lt;/p&gt;

&lt;p&gt;Once the deployment is complete, you'll be able to see an auto-generated API key (e.g., &lt;code&gt;sk-agent-aa3f96a3-43e9-448f-ad94-84a38e64c229&lt;/code&gt;). Save this key in a secure location. You can generate a new key at any time from the project dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Add environment variables
&lt;/h3&gt;

&lt;p&gt;When the deployment is complete, you will also be able to add environment variables to the agent. These will be injected at runtime with each request to the agent endpoint. Add the OpenAI API key as an environment variable from the UI.&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%2F6euh48njsh953jxmd7sj.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%2F6euh48njsh953jxmd7sj.png" alt="Adding environment variables" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Initiate the agent
&lt;/h3&gt;

&lt;p&gt;Now that the agent is deployed, you can initiate one or more instances of it by sending a POST request to the agent endpoint. From the project dashboard, you can see the agent endpoint URL (e.g., &lt;code&gt;https://&amp;lt;your-agent-slug&amp;gt;.agent.itura.ai/run&lt;/code&gt;). Using this URL and the API key, you can initiate the agent by sending a POST request to the endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; POST &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://&lt;span class="o"&gt;{&lt;/span&gt;agentSlug&lt;span class="o"&gt;}&lt;/span&gt;.agent.itura.ai/run &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer &amp;lt;token&amp;gt;'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
  "input": "Your agent parameters here"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If successful, you'll receive a &lt;code&gt;202 Accepted&lt;/code&gt; response. This means your request is queued.&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="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"run_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unique-run-id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Run request accepted and queued for execution"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PENDING"&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 can check the status of the run from Itura's dashboard. Or, by sending a GET request to the &lt;code&gt;/status&lt;/code&gt; endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;* The status endpoint is provided by Itura platform. So, you don't need to add it to your code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--request&lt;/span&gt; GET &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--url&lt;/span&gt; https://&lt;span class="o"&gt;{&lt;/span&gt;agentSlug&lt;span class="o"&gt;}&lt;/span&gt;.agent.itura.ai/status/&lt;span class="o"&gt;{&lt;/span&gt;run_id&lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Authorization: Bearer &amp;lt;token&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;That's it! You've just deployed a CrewAI agent to the cloud and initiated it using a simple HTTP request. From the Itura dashboard, you can see the agent's logs, metrics, and more. Though not covered in this guide, you can also parse the POST request body to the agent. This way, you can pass more complex data to the agent as input.&lt;/p&gt;

&lt;p&gt;If you want to update the deployment code, you can do so by pushing a new commit to the GitHub repository. Itura will automatically detect the changes and update the deployment.&lt;/p&gt;

&lt;p&gt;To learn more about itura, visit &lt;a href="https://itura.ai" rel="noopener noreferrer"&gt;itura.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>rpa</category>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Spring Boot and React in Harmony</title>
      <dc:creator>Tarek Oraby</dc:creator>
      <pubDate>Sat, 30 Sep 2023 16:59:34 +0000</pubDate>
      <link>https://forem.com/tarekoraby/spring-boot-and-react-in-harmony-1che</link>
      <guid>https://forem.com/tarekoraby/spring-boot-and-react-in-harmony-1che</guid>
      <description>&lt;p&gt;For many full-stack developers, the combination of Spring Boot and React has become a staple in building dynamic business applications. Yet, while powerful, this pairing has its set of challenges. From type-related errors to collaboration hurdles, developers often find themselves navigating a maze of everyday issues.&lt;/p&gt;

&lt;p&gt;Enter &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt;, a framework that aims to simplify this landscape. If Hilla hasn't crossed your radar yet, this article will provide an overview of what it offers and how it can potentially streamline your development process when working with Spring Boot and React.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spring Boot, React, and Hilla
&lt;/h2&gt;

&lt;p&gt;For full-stack developers, the combination of Java on the backend and React (with TypeScript) on the frontend offers a compelling blend of reliability and dynamism. Java, renowned for its robust type system, ensures data behaves predictably, catching potential errors at compile-time. Meanwhile, TypeScript brings a similar layer of type safety to the JavaScript world, enhancing React's capabilities and ensuring components handle data as expected.&lt;/p&gt;

&lt;p&gt;However, while both Java and TypeScript offer individual type-safe havens, there's often a missing link: ensuring that this type-safety is consistent from the backend all the way to the frontend. This is where the benefits of &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt; shine, enabling&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;End-to-End Type Safety&lt;/li&gt;
&lt;li&gt;Direct Communication Between React and Spring Services&lt;/li&gt;
&lt;li&gt;Consistent Data Validation and Type Safety&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  End-To-End Type Safety
&lt;/h2&gt;

&lt;p&gt;Hilla takes type safety a step further by ensuring it spans the entire development spectrum. Developers spend less time perusing API documentation and more time coding. With automatically generated TypeScript services and data types, &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt; allows developers to explore APIs directly within their IDE. This seamless integration means that if any code is altered, whether on the frontend or backend, any inconsistencies will trigger a compile-time error, ensuring that issues are caught early and rectified. &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%2Fxy4074cuz6qy3q58u9xk.jpg" 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%2Fxy4074cuz6qy3q58u9xk.jpg" alt="Hilla auto-complete" width="800" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Direct Communication Between React and Spring Services
&lt;/h2&gt;

&lt;p&gt;With &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt;, the cumbersome process of managing endpoints or deciphering complex queries becomes a thing of the past. Developers can directly call Spring Boot services from their React client, receiving precisely what's needed. This is achieved by making a Spring &lt;code&gt;@Service&lt;/code&gt; available to the browser using Hilla's &lt;code&gt;@BrowserCallable&lt;/code&gt; annotation. This direct communication streamlines data exchange, ensuring that the frontend gets exactly what it expects without any unnecessary overhead.&lt;/p&gt;

&lt;p&gt;Here's how it works. First, you add &lt;code&gt;@BrowserCallable&lt;/code&gt; annotation to your Spring Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@BrowserCallable&lt;/span&gt; 
&lt;span class="nd"&gt;@Service&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerService&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;getCustomers&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Fetch customers from DB or API&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Based on this annotation, &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt; auto-generates TypeScript types and clients that enable calling the Java backend in a type-checkable way from the frontend (no need to declare any REST endpoints):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CustomerList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Customer type is automatically generated by Hilla&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setCustomers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

    &lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;CustomerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getCustomers&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setCustomers&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ComboBox&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;customers&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ComboBox&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;
  
  
  Consistent Data Validation and Type Safety
&lt;/h2&gt;

&lt;p&gt;One of the standout features of &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt; is its ability to maintain data validation consistency across the stack. By defining data validation rules once on the backend, Hilla auto-generates TypeScript validations for the frontend. This not only enhances developer productivity but also ensures that data remains consistent, regardless of where it's being processed. &lt;/p&gt;

&lt;p&gt;For instance, if a field is marked as &lt;code&gt;@NotBlank&lt;/code&gt; in Java, Hilla ensures that the same validation is applied when this data is processed in the React frontend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Name is mandatory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@NotBlank&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Email is mandatory"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@Email&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Getters and setters&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Hilla &lt;code&gt;useForm&lt;/code&gt; hook uses the generated TypeScript model to apply the validation rules to the form fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;CustomerForm&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CustomerModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;onSubmit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CustomerService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveCustomer&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TextField&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;EmailField&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;{...&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;      &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="nx"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;Save&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Button&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;
  
  
  Batteries and Guardrails Included
&lt;/h2&gt;

&lt;p&gt;Hilla streamlines full-stack development by offering pre-built tools, enhancing real-time capabilities, prioritizing security, and ensuring long-term adaptability.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;framework&lt;/a&gt; provides a set of pre-built UI components designed specifically for data-intensive applications. These components range from data grids and forms to various select components and editors. Moreover, for those looking to implement real-time features, Hilla simplifies the process with its support for reactive endpoints, removing the need for manual WebSocket management. Another notable aspect of Hilla is its security integration. By default, it's connected with Spring Security, offering robust access control mechanisms to safeguard data exchanges. The framework's stateless design ensures that as user demands increase, the application remains efficient. &lt;/p&gt;

&lt;p&gt;Hilla's design approach not only streamlines the current development process but also future-proofs your app. It ensures that all components integrate seamlessly, making updates, especially transitioning from one version to another, straightforward and hassle-free.&lt;/p&gt;

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

&lt;p&gt;Navigating the complexities of full-stack development in Spring Boot and React can be complex. This article highlighted how Hilla can alleviate many of these challenges. From ensuring seamless type safety to simplifying real-time integrations and bolstering security, Hilla stands out as a comprehensive solution. Its forward-thinking design ensures that as the tech landscape evolves, your applications remain adaptable and updates remain straightforward. &lt;/p&gt;

&lt;p&gt;For those immersed in the world of Spring Boot and React, considering &lt;a href="https://hilla.dev/" rel="noopener noreferrer"&gt;Hilla&lt;/a&gt; might be a step in the right direction. It's more than just a framework; it's a pathway to streamlined and future-ready development.&lt;/p&gt;

</description>
      <category>springboot</category>
      <category>react</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>How to style a Vaadin Application</title>
      <dc:creator>Tarek Oraby</dc:creator>
      <pubDate>Fri, 11 Mar 2022 08:40:31 +0000</pubDate>
      <link>https://forem.com/tarekoraby/how-to-style-a-vaadin-application-31bd</link>
      <guid>https://forem.com/tarekoraby/how-to-style-a-vaadin-application-31bd</guid>
      <description>&lt;p&gt;In this guide, we learn the basics of styling a Vaadin application using Cascading Style Sheets (CSS).&lt;/p&gt;

&lt;p&gt;While it’s possible to style some parts of a Vaadin application using the Java API, this approach tends to be rather limited for mid- to large-sized applications. So, it is recommended to do the styling of a Vaadin application by adding CSS inside standard style sheets. This guide focuses only on the recommended style-sheets approach.&lt;/p&gt;

&lt;p&gt;The following topics are discussed in this guide:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a custom theme&lt;/li&gt;
&lt;li&gt;Where to place your CSS&lt;/li&gt;
&lt;li&gt;How to override Lumo values&lt;/li&gt;
&lt;li&gt;How to selectively style views and components&lt;/li&gt;
&lt;li&gt;When to add CSS under the /components directory&lt;/li&gt;
&lt;li&gt;How to selectively style the internals of a Vaadin component&lt;/li&gt;
&lt;li&gt;How to selectively style a sub-component of a Vaadin component&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Create a custom theme
&lt;/h2&gt;

&lt;p&gt;To start, we need to inform Vaadin about the location in which we will place our CSS. The easiest way to do this is to add the &lt;code&gt;@Theme&lt;/code&gt; annotation on a class that implements &lt;code&gt;AppShellConfigurator&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;For example, in a Spring Boot application, you could have an application configuration class such as the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Theme&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"myapp"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;SpringBootServletInitializer&lt;/span&gt;
                         &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;AppShellConfigurator&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;SpringApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, projects downloaded from &lt;a href="https://start.vaadin.com/" rel="noopener noreferrer"&gt;start.vaadin.com&lt;/a&gt; come with the &lt;code&gt;@Theme&lt;/code&gt; annotation added to the &lt;code&gt;Application&lt;/code&gt; class.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to place your CSS
&lt;/h2&gt;

&lt;p&gt;Custom CSS should be placed inside a specific folder located under the &lt;code&gt;frontend/themes&lt;/code&gt; directory in your project. This directory has something like the following minimal structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frontend
└── themes              
    └── myapp        
        ├── components/ 
        └── styles.css
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; the &lt;code&gt;myapp&lt;/code&gt; sub-folder might be named differently in your case. This folder should have the same name as the one used for the custom theme using the &lt;code&gt;@Theme&lt;/code&gt; annotation.&lt;/p&gt;

&lt;p&gt;Custom CSS should be placed in one of the following locations: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Inside the &lt;code&gt;styles.css&lt;/code&gt; file&lt;/li&gt;
&lt;li&gt;As a separate CSS file, which is imported from within &lt;code&gt;styles.css&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Under the &lt;code&gt;components/&lt;/code&gt; directory in order to style the (local CSS) internals of Vaadin components (such as a Vaadin Button) &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first and simplest option is to place the custom CSS directly inside the &lt;code&gt;styles.css&lt;/code&gt; file. For example, adding the following snippet to &lt;code&gt;styles.css&lt;/code&gt; would change the font family and size of the whole Vaadin application.&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="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;font-family&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"Lucida Console"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;"Courier New"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;monospace&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;1rem&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, as the number of custom stylings increases, it becomes convenient to organize the styles into separate &lt;code&gt;.css&lt;/code&gt; files. In order to do this, simply create a new CSS file and import that from within &lt;code&gt;styles.css&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For example, if we create a new CSS file called &lt;code&gt;main-view.css&lt;/code&gt; at &lt;code&gt;frontend/themes/myapp/views/main-view.css&lt;/code&gt;, then we would import this file by adding the following line to &lt;code&gt;styles.css&lt;/code&gt;:&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="k"&gt;@import&lt;/span&gt; &lt;span class="sx"&gt;url('./views/main-view.css')&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Further below, we will discuss the scenarios in which we should add our CSS to the &lt;code&gt;components/&lt;/code&gt; directory.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to override Lumo values
&lt;/h2&gt;

&lt;p&gt;The default look and feel of a Vaadin application is based on a built-in theme, called Lumo. This theme is essentially a bunch of CSS custom properties (such as colors, typography, and sizes) that each has a CSS variable assigned to it.&lt;/p&gt;

&lt;p&gt;One easy way to customize the look and feel of our application requires us to override the default values of the Lumo variables.&lt;/p&gt;

&lt;p&gt;For example, suppose our application has a bunch of TextField, ComboBox, DatePicker, and Button components. By default, they will look as follows:&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%2Fgle8hdd9f89lwqus4mb8.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%2Fgle8hdd9f89lwqus4mb8.png" alt="A Vaadin TextField, ComboBox, DatePicker, and Button with default look and feel." width="800" height="108"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Suppose that we want to increase the roundedness of their corners. By default, the Lumo theme provides these components with a small rounded corner whose value is defined in the &lt;code&gt;--lumo-border-radius-m&lt;/code&gt; variable. In order to increase the roundedness of the corners, we can override the default value of this variable. Specifically, we can add the following inside the &lt;code&gt;styles.css&lt;/code&gt; file in order to increase the default roundedness value:&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="nt"&gt;html&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--lumo-border-radius-m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&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;This style will increase the corner roundedness of many components at once, so that they will look similar to the following screenshot:&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%2Fjlqwo3qrr1gac3s94i6g.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%2Fjlqwo3qrr1gac3s94i6g.png" alt="A Vaadin TextField, ComboBox, DatePicker, and Button with increased corner roundedness." width="800" height="87"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But what if one wants to override the defaults for only a subset of components? No problem; simply use the name of the components as the CSS selector. &lt;/p&gt;

&lt;p&gt;For example, if one wants to override the rounded corner defaults for only the TextField and ComboBox components, then the following should be added inside the &lt;code&gt;styles.css&lt;/code&gt; file:&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="nt"&gt;vaadin-text-field&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;vaadin-combo-box&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="py"&gt;--lumo-border-radius-m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1em&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;This will change the defaults for the TextField and ComboBox only, leaving other components, such as the DatePicker and Button, with their default values.&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%2Flrjca5ijn3rdfa2szh66.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%2Flrjca5ijn3rdfa2szh66.png" alt="A Vaadin TextField, ComboBox with increased corner roundedness. A Vaadin DatePicker, and Button with default look and feel." width="800" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to selectively style views and components
&lt;/h2&gt;

&lt;p&gt;So far, we have been discussing ways to style all components belonging to a certain kind in the same way. For example, if we want to set the width of all Button components within our application, we could add something like the following to the &lt;code&gt;styles.css&lt;/code&gt; file:&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="nt"&gt;vaadin-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;140px&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, we often want to style the same kind of component differently, depending on its function within our application. For example, we might want to style the Button component differently in our application, depending on whether it is a “Signup” button or a “Delete account!” button. &lt;/p&gt;

&lt;p&gt;The easiest way to style views and components selectively is by providing them with a CSS class name from the Java API. All Vaadin components and views have an &lt;code&gt;addClassNames()&lt;/code&gt; method that can be used for this purpose.&lt;/p&gt;

&lt;p&gt;To illustrate, the following view makes use of the &lt;code&gt;addClassNames()&lt;/code&gt; method to assign a CSS class name to the view itself, as well as to the components that it includes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;com.vaadin.flow.component.button.Button&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;com.vaadin.flow.component.html.H1&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;com.vaadin.flow.component.html.Paragraph&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;com.vaadin.flow.component.orderedlayout.VerticalLayout&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;com.vaadin.flow.router.Route&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@Route&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyView&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;VerticalLayout&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MyView&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;addClassNames&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"my-view"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//CSS classname for the whole view&lt;/span&gt;

        &lt;span class="no"&gt;H1&lt;/span&gt; &lt;span class="n"&gt;heading&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="no"&gt;H1&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"My View Title"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;Paragraph&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Paragraph&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Thanks for shopping with us. Click the button to submit your order."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="n"&gt;submitButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Submit"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;submitButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addClassNames&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"submit-button"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"wide-button"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//CSS classnames for the submit button&lt;/span&gt;

        &lt;span class="nc"&gt;Button&lt;/span&gt; &lt;span class="n"&gt;cancelButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Cancel"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;cancelButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addClassNames&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cancel-button"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"wide-button"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;//CSS classnames for the cancel button&lt;/span&gt;

        &lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heading&lt;/span&gt;&lt;span class="o"&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;submitButton&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancelButton&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These CSS class names can then be used to selectively style this view and its components. For example, adding the following to &lt;code&gt;styles.css&lt;/code&gt; demonstrates how selective styling can be achieved.&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;.my-view&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;1em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.my-view&lt;/span&gt; &lt;span class="nt"&gt;h1&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/*Only applies to the h1 headers inside my-view*/&lt;/span&gt;
    &lt;span class="nl"&gt;margin-top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;p&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;/*Applies to all p elements regardless of whether they are
     located inside my-view*/&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;1.2em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.submit-button&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;green&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.cancel-button&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;red&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;.wide-button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;120px&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 after the selective styling is applied, &lt;code&gt;MyView&lt;/code&gt; will look as follows:&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%2Fij7493vc0zx9vxhoza3p.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%2Fij7493vc0zx9vxhoza3p.png" alt="MyView after selective styling" width="745" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  When to add CSS under the &lt;code&gt;/components&lt;/code&gt; directory
&lt;/h2&gt;

&lt;p&gt;Customizing styling using the two aforementioned options (namely by placing styles inside &lt;code&gt;styles.css&lt;/code&gt;, or inside a separate file imported from within &lt;code&gt;styles.css&lt;/code&gt;) should work for views and layouts created within the project. However, these two options are not guaranteed to work if one aims to style the internals of a Vaadin component. &lt;/p&gt;

&lt;p&gt;Custom styling of the internals of Vaadin components, such as the Grid or ComboBox components, needs to be placed under the &lt;code&gt;frontend/themes/myapp/components/&lt;/code&gt; directory. (Note again that the &lt;code&gt;myapp&lt;/code&gt; folder name might be different in your case.) &lt;/p&gt;

&lt;p&gt;For example, let’s assume we want to increase the size of the toggle icon that opens the dropdown menu of a ComboBox.&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%2F4i8koa51nx4fol9sko1i.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%2F4i8koa51nx4fol9sko1i.png" alt="ComboBox with an arrow pointing at toggle" width="374" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we inspect this toggle in Chrome (right-click on the toggle and select the &lt;strong&gt;Inspect&lt;/strong&gt; option), we will see that it has an attribute called &lt;code&gt;part&lt;/code&gt; whose value is equal to &lt;code&gt;toggle-button&lt;/code&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%2Fta1xltpw3emxd76521cu.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%2Fta1xltpw3emxd76521cu.png" alt="A screenshot of a div Element in the DOM with a highlighted part attribute whose value is equal to toggle-button" width="565" height="26"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To style this part, we need to create a file called &lt;code&gt;vaadin-combo-box.css&lt;/code&gt; and place it under the &lt;code&gt;frontend/themes/myapp/components/&lt;/code&gt; directory. In this file, we can do something like the following to increase the size of the ComboBox toggle.&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="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"toggle-button"&lt;/span&gt;&lt;span class="o"&gt;]&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;2em&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;Note that the files placed under the &lt;code&gt;/components&lt;/code&gt; directory have to exactly match the Vaadin component names as they appear in the DOM. For example, the ComboBox component appears in the DOM as &lt;code&gt;vaadin-combo-box&lt;/code&gt;. Accordingly, the styling file of the ComboBox has to be named exactly &lt;code&gt;vaadin-combo-box.css&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to selectively style the internals of a Vaadin component
&lt;/h2&gt;

&lt;p&gt;We just saw how to style the internals of a Vaadin component by using the &lt;code&gt;part&lt;/code&gt; property and placing the CSS inside a specific file placed under the &lt;code&gt;frontend/themes/myapp/components/&lt;/code&gt; directory. Specifically, we saw how to style the ComboBox toggle using the &lt;code&gt;[part="toggle-button"]&lt;/code&gt; selector and placing the CSS in a file called &lt;code&gt;vaadin-combo-box.css&lt;/code&gt; that resides under the &lt;code&gt;frontend/themes/myapp/components/&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;However, this approach will style all toggle elements of all the ComboBox components in our application in the same way. What if we want our styling to apply to only some toggle elements but not to others?&lt;/p&gt;

&lt;p&gt;Here we can follow the same approach as we did above to selectively style views and components. That is, we can provide the component with a CSS class name from the Java API using the &lt;code&gt;addClassNames()&lt;/code&gt; method. Then we can use this CSS class name to selectively style the internal part of the component.&lt;/p&gt;

&lt;p&gt;For example, suppose that our application has the following two ComboBox components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;firstCombo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;comboBox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Earth"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mars"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;secondCombo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;comboBox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Mercury"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Venus"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If we want to style the toggle part of these two components differently, we can do this by providing each ComboBox with a different classname as follows&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;firstCombo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addClassNames&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first-combo"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;secondCombo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addClassNames&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"second-combo"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in &lt;code&gt;frontend/themes/myapp/components/vaadin-combo-box.css&lt;/code&gt;, we can selectively style the toggle element of the two components differently by doing something like:&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="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.first-combo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"toggle-button"&lt;/span&gt;&lt;span class="o"&gt;]&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;2em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;.second-combo&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"toggle-button"&lt;/span&gt;&lt;span class="o"&gt;]&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;1em&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;Notice that we have to use here the &lt;code&gt;:host()&lt;/code&gt; shadow function because the &lt;code&gt;toggle-button&lt;/code&gt; part is located inside the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM" rel="noopener noreferrer"&gt;shadow DOM&lt;/a&gt; of the ComboBox.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to selectively style a sub-component of a Vaadin component
&lt;/h2&gt;

&lt;p&gt;There are cases in which we cannot use CSS classnames to do the selective styling. Specifically, we cannot rely on CSS classnames when we want to style the sub-components of a Vaadin component.&lt;/p&gt;

&lt;p&gt;What do I mean by a sub-component? Some Vaadin components create sub-components that exist in the DOM independently from the Vaadin component itself. This is noticeably the case for the Vaadin components, such as the DatePicker, Dialog, and ComboBox, that open an overlay during user interaction. For example, a ComboBox adds a &lt;code&gt;vaadin-combo-box-overlay&lt;/code&gt; element in the DOM when the user clicks on the toggle button to see the list of the available options. This &lt;code&gt;vaadin-combo-box-overlay&lt;/code&gt; element exists in the DOM independently from the  &lt;code&gt;vaadin-combo-box&lt;/code&gt; element itself.&lt;/p&gt;

&lt;p&gt;The fact that they exist in the DOM independently from their parent component means that a CSS classname that is given to the parent element is not propagated down to the sub-component. For example, if we give a ComboBox a classname from the Java API, only the  &lt;code&gt;vaadin-combo-box&lt;/code&gt; element will get this classname in the DOM, not the &lt;code&gt;vaadin-combo-box-overlay&lt;/code&gt; element.&lt;/p&gt;

&lt;p&gt;Instead of classnames, we can rely on the &lt;code&gt;theme&lt;/code&gt; attribute as a selector in order to selectively style a sub-component of a Vaadin component. Unlike the classnames, the &lt;code&gt;theme&lt;/code&gt; attribute has the benefit of propagating through to the sub-components.&lt;/p&gt;

&lt;p&gt;We can provide a component with a theme attribute from the Java API using the &lt;code&gt;addThemeName()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;For example, like before suppose that our application has the following two ComboBox components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;firstCombo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;firstCombo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Earth"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Mars"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;secondCombo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ComboBox&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
&lt;span class="n"&gt;secondCombo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setItems&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Mercury"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Venus"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suppose that we want to change the background color of the drop-down list of items that the user sees when they open the ComboBox. This background color is controlled by the &lt;code&gt;background-color&lt;/code&gt; property of the &lt;code&gt;overlay&lt;/code&gt; part of the &lt;code&gt;vaadin-combo-box-overlay&lt;/code&gt; element. This &lt;code&gt;overlay&lt;/code&gt; part in the DOM is highlighted in the following screenshot.&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%2F50ek9lzuxks0rvt39y6n.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%2F50ek9lzuxks0rvt39y6n.png" alt="Vaadin combo-box-overlay element in the DOM with the overlay part highlighted" width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To selectively style the background of the overlay of the two ComboBox components, we can first give each one of them a theme name using the Java API as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;firstCombo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addThemeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"first-combo"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;secondCombo&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addThemeName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"second-combo"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, we need to create a new file called &lt;code&gt;vaadin-combo-box-overlay.css&lt;/code&gt; and place it under the &lt;code&gt;frontend/themes/myapp/components&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;In this file, we can selectively style the overlay part of the &lt;code&gt;vaadin-combo-box-overlay&lt;/code&gt; element of the two ComboBoxes by doing something like:&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="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"first-combo"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"overlay"&lt;/span&gt;&lt;span class="o"&gt;]&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;rosybrown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;:host&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;&lt;span class="nt"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"second-combo"&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"overlay"&lt;/span&gt;&lt;span class="o"&gt;]&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;aquamarine&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;Here again we are using the &lt;code&gt;:host()&lt;/code&gt; shadow function because the overlay part is located inside the shadow DOM of the &lt;code&gt;vaadin-combo-box-overlay&lt;/code&gt; element.&lt;/p&gt;

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

&lt;p&gt;In this guide, we learned the basics of styling a Vaadin application using CSS. Be sure to check the official Vaadin &lt;a href="https://vaadin.com/docs/latest/ds/overview" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more advanced use cases.&lt;/p&gt;

</description>
      <category>vaadin</category>
      <category>java</category>
      <category>css</category>
    </item>
  </channel>
</rss>
