diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/README.md b/rio/snippets/snippet-files/project-template-Simple CRUD/README.md
new file mode 100644
index 00000000..30404ce4
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/README.md
@@ -0,0 +1 @@
+TODO
\ No newline at end of file
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/__init__.py b/rio/snippets/snippet-files/project-template-Simple CRUD/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/components/__init__.py b/rio/snippets/snippet-files/project-template-Simple CRUD/components/__init__.py
new file mode 100644
index 00000000..8843b585
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/components/__init__.py
@@ -0,0 +1,2 @@
+from .item_editor import ItemEditor
+from .item_list import ItemList
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/components/item_editor.py b/rio/snippets/snippet-files/project-template-Simple CRUD/components/item_editor.py
new file mode 100644
index 00000000..13bbc569
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/components/item_editor.py
@@ -0,0 +1,144 @@
+import rio
+
+
+#
+from .. import models
+
+#
+
+
+#
+class ItemEditor(rio.Component):
+ """
+ A component for editing a menu item.
+
+ Returns a card component containing the menu item editor. The editor contains fields
+ for name, description, price, and category of the menu item. The component also contains
+ buttons for saving or canceling the changes.
+
+ Attributes:
+ currently_selected_menu_item: The currently selected menu item.
+ new_entry: A boolean flag indicating if the menu item is a new entry.
+ on_cancel_event: An event handler for the cancel button.
+ on_save_event: An event handler for the save button.
+ """
+
+ currently_selected_menu_item: models.MenuItems
+ new_entry: bool = False
+ on_cancel_event: rio.EventHandler[[]] = None
+ on_save_event: rio.EventHandler[[]] = None
+
+ async def on_press_save_event(self) -> None:
+ """
+ Asynchronously triggers the 'on_save_event' when the save button is pressed.
+ """
+ await self.call_event_handler(self.on_save_event)
+
+ async def on_press_cancel_event(self) -> None:
+ """
+ Asynchronously triggers the 'on_cancel_event' when the cancel button is pressed.
+ """
+ await self.call_event_handler(self.on_cancel_event)
+
+ def on_change_name(self, ev: rio.TextInputChangeEvent) -> None:
+ """
+ Changes the name of the currently selected menu item.
+
+ Args:
+ ev: The event object that contains the new text.
+ """
+ self.currently_selected_menu_item.name = ev.text
+
+ def on_change_description(self, ev: rio.TextInputChangeEvent) -> None:
+ """
+ Changes the description of the currently selected menu item.
+
+ Args:
+ ev: The event object that contains the new text.
+ """
+ self.currently_selected_menu_item.description = ev.text
+
+ def on_change_price(self, ev: rio.NumberInputChangeEvent) -> None:
+ """
+ Changes the price of the currently selected menu item.
+
+ Args:
+ ev: The event object that contains the new price.
+ """
+ self.currently_selected_menu_item.price = ev.value
+
+ def on_change_category(self, ev: rio.DropdownChangeEvent) -> None:
+ """
+ Changes the category of the currently selected menu item.
+
+ Args:
+ ev: The event object that contains the new category.
+ """
+ self.currently_selected_menu_item.category = ev.value
+
+ def build(self) -> rio.Component:
+ """
+ Builds the menu item editor component.
+
+ Returns:
+ A card component containing the menu item editor.
+ See the approx. layout below:
+
+ ################ Card #################
+ # Text #
+ # TextInput (Name) #
+ # TextInput (Description) #
+ # NumberInput (Price) #
+ # Dropdown (Category) #
+ # Button (Save) | Button (Cancel) #
+ #######################################
+ """
+
+ if self.new_entry is False:
+ text = "Edit Menu Item"
+ else:
+ text = "Add New Menu Item"
+
+ return rio.Card(
+ rio.Column(
+ rio.Text(
+ text=text,
+ style="heading2",
+ margin_bottom=1,
+ ),
+ rio.TextInput(
+ self.currently_selected_menu_item.name,
+ label="Name",
+ on_change=self.on_change_name,
+ ),
+ rio.TextInput(
+ self.currently_selected_menu_item.description,
+ label="Description",
+ on_change=self.on_change_description,
+ ),
+ rio.NumberInput(
+ self.currently_selected_menu_item.price,
+ label="Price",
+ suffix_text="$",
+ on_change=self.on_change_price,
+ ),
+ rio.Dropdown(
+ options=["Burgers", "Desserts", "Drinks", "Salads", "Sides"],
+ label="Category",
+ selected_value=self.currently_selected_menu_item.category,
+ on_change=self.on_change_category,
+ ),
+ rio.Row(
+ rio.Button("Save", on_press=self.on_press_save_event),
+ rio.Button("Cancel", on_press=self.on_press_cancel_event),
+ spacing=1,
+ align_x=1,
+ ),
+ spacing=1,
+ align_y=0,
+ margin=2,
+ ),
+ )
+
+
+#
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/components/item_list.py b/rio/snippets/snippet-files/project-template-Simple CRUD/components/item_list.py
new file mode 100644
index 00000000..823fff61
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/components/item_list.py
@@ -0,0 +1,116 @@
+import rio
+
+#
+import functools
+from .. import models
+
+#
+
+
+#
+class ItemList(rio.Component):
+ """
+ A component for displaying a list of menu items.
+
+ Returns a list of menu items in a ListView component. Each item in the list contains the name,
+ description, and a delete button. The delete button triggers an event to delete the item.
+
+ Attributes:
+ menu_item_set: The list of menu items to be displayed.
+ on_add_new_item_event: An event handler for adding a new item.
+ on_delete_item_event: An event handler for deleting an item.
+ on_select_item_event: An event handler for selecting an item.
+ """
+
+ menu_item_set: list[models.MenuItems]
+ on_add_new_item_event: rio.EventHandler[[]] = None
+ on_delete_item_event: rio.EventHandler[int] = None
+ on_select_item_event: rio.EventHandler[models.MenuItems] = None
+
+ async def on_press_add_new_item_event(self) -> None:
+ """
+ Asynchronously triggers the 'add new item' when the list item is pressed.
+ """
+ await self.call_event_handler(self.on_add_new_item_event)
+
+ async def on_press_delete_item_event(self, idx: int) -> None:
+ """
+ Asynchronously triggers the 'delete item' when the delete button is pressed.
+ The event handler is passed the index of the item to be deleted.
+
+ Args:
+ idx: The index of the item to be deleted.
+ """
+ await self.call_event_handler(self.on_delete_item_event, idx)
+ # update the list
+ await self.force_refresh()
+
+ async def on_press_select_item_event(self, item: models.MenuItems) -> None:
+ """
+ Asynchronously triggers the 'select item' when an item is selected.
+ The event handler is passed the selected item.
+
+ Args:
+ item: The selected item.
+ """
+ await self.call_event_handler(self.on_select_item_event, item)
+
+ def build(self) -> rio.Component:
+ """
+ Builds the component by returning a ListView component containing the menu items.
+
+ Returns:
+ A ListView component containing the menu items.
+ See the approx. layout below:
+
+ ############### ListView ###############
+ # + Add new #
+ # Hamburger Button(Delete) #
+ # Cheese Burger Button(Delete) #
+ # Fries Button(Delete) #
+ # ... #
+ ########################################
+ """
+
+ # Store all children in an intermediate list
+ list_items = []
+
+ list_items.append(
+ rio.SimpleListItem(
+ text="Add new",
+ secondary_text="Description",
+ key="add_new",
+ left_child=rio.Icon("material/add"),
+ on_press=self.on_press_add_new_item_event,
+ )
+ )
+
+ for i, item in enumerate(self.menu_item_set):
+ list_items.append(
+ rio.SimpleListItem(
+ text=item.name,
+ secondary_text=item.description,
+ right_child=rio.Button(
+ rio.Icon("material/delete"),
+ color=self.session.theme.danger_color,
+ # Note the use of functools.partial to pass the
+ # index to the event handler.
+ on_press=functools.partial(self.on_press_delete_item_event, i),
+ ),
+ # Use the name as the key to ensure that the list item
+ # is unique.
+ key=item.name,
+ # Note the use of functools.partial to pass the
+ # item to the event handler.
+ on_press=functools.partial(self.on_press_select_item_event, item),
+ )
+ )
+
+ # Then unpack the list to pass the children to the ListView
+ return rio.ListView(
+ *list_items,
+ align_y=0,
+ )
+
+
+#
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/models.py b/rio/snippets/snippet-files/project-template-Simple CRUD/models.py
new file mode 100644
index 00000000..4bfc7541
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/models.py
@@ -0,0 +1,76 @@
+from dataclasses import dataclass
+from typing import * # type:ignore
+
+
+@dataclass
+class MenuItems:
+ """
+ MenuItems data model.
+
+ Attributes:
+ name: The name of the menu item.
+ description: The description of the menu item.
+ price: The price of the menu item.
+ category: The category of the menu item.
+ """
+
+ name: str
+ description: str
+ price: float
+ category: str
+
+ @staticmethod
+ def new_empty() -> "MenuItems":
+ """
+ Creates a new empty MenuItems object.
+
+ Returns:
+ MenuItems: A new empty MenuItems object.
+ """
+ return MenuItems(
+ name="",
+ description="",
+ price=0.0,
+ category="",
+ )
+
+
+# initial data
+MENUITEMSET: list[MenuItems] = [
+ MenuItems(
+ name="Hamburger",
+ description="A classic hamburger with lettuce, tomato, and onions",
+ price=4.99,
+ category="Burgers",
+ ),
+ MenuItems(
+ name="Cheeseburger",
+ description="A classic cheeseburger with lettuce, tomato, onions and cheese",
+ price=5.99,
+ category="Burgers",
+ ),
+ MenuItems(
+ name="Fries",
+ description="A side of crispy fries",
+ price=2.99,
+ category="Sides",
+ ),
+ MenuItems(
+ name="Soda",
+ description="A refreshing soda",
+ price=1.99,
+ category="Drinks",
+ ),
+ MenuItems(
+ name="Salad",
+ description="A fresh salad",
+ price=4.99,
+ category="Salads",
+ ),
+ MenuItems(
+ name="Ice Cream",
+ description="A sweet treat",
+ price=3.99,
+ category="Desserts",
+ ),
+]
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/pages/__init__.py b/rio/snippets/snippet-files/project-template-Simple CRUD/pages/__init__.py
new file mode 100644
index 00000000..c1d4daa2
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/pages/__init__.py
@@ -0,0 +1 @@
+from .crud_page import CrudPage
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/pages/crud_page.py b/rio/snippets/snippet-files/project-template-Simple CRUD/pages/crud_page.py
new file mode 100644
index 00000000..4f8bfa12
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/pages/crud_page.py
@@ -0,0 +1,178 @@
+import rio
+
+
+#
+from .. import components as comps
+from .. import models
+
+#
+
+
+#
+class CrudPage(rio.Component):
+ """
+ A CRUD page that allows users to create, read, update, and delete menu items.
+
+ This component is composed of a Banner component, an ItemList component, and an ItemEditor component.
+
+ The @rio.event.on_populate decorator is used to fetch data from a predefined data model and assign it to the
+ menu_item_set attribute of the current instance. The on_press_delete_item, on_press_cancel_event, and
+ on_press_save_event methods are used to handle delete, cancel, and save events, respectively. The on_press_add_new_item
+ method is used to handle the add new item event. The on_press_select_menu_item method is used to handle the selection
+ of a menu item.
+
+ Attributes:
+ menu_item_set: A list of menu items.
+ currently_selected_menu_item: The currently selected menu item.
+ banner_text: The text to be displayed in the banner.
+ is_new_entry: A flag to indicate if the currently selected menu item is a new entry.
+ """
+
+ menu_item_set: list[models.MenuItems] = []
+ currently_selected_menu_item: models.MenuItems | None = None
+ banner_text: str = ""
+ is_new_entry: bool = False
+
+ @rio.event.on_populate
+ def on_populate(self) -> None:
+ """
+ Event handler that is called when the component is populated.
+
+ Fetches data from a predefined data model and assigns it to the menu_item_set
+ attribute of the current instance.
+ """
+ self.menu_item_set = models.MENUITEMSET
+
+ async def on_press_delete_item(self, idx: int) -> None:
+ """
+ Perform actions when the "Delete" button is pressed.
+
+ Args:
+ idx: The index of the item to be deleted.
+ """
+ self.menu_item_set.pop(idx)
+ self.banner_text = "Item was deleted"
+ self.currently_selected_menu_item = None
+
+ async def on_press_cancel_event(self) -> None:
+ """
+ Perform actions when the "Cancel" button is pressed.
+ """
+ self.currently_selected_menu_item = None
+ self.banner_text = ""
+
+ def on_press_save_event(self) -> None:
+ """
+ Performs actions when the "Save" button is pressed.
+
+ This method appends the currently selected menu item to the menu item set if it is a new entry,
+ or updates the menu item set if it is an existing entry. It also updates the banner text and sets
+ the is_new_entry flag to False.
+ """
+ assert self.currently_selected_menu_item is not None
+ if self.is_new_entry:
+ self.menu_item_set.append(self.currently_selected_menu_item)
+ self.banner_text = "Item was added"
+ self.is_new_entry = False
+ self.currently_selected_menu_item = None
+ else:
+ self.banner_text = "Item was updated"
+
+ async def on_press_add_new_item(self) -> None:
+ """
+ Perform actions when the "Add New" ListItem is pressed.
+
+ This method sets the currently selected menu item to a new empty instance of models.MenuItems,
+ clears the banner text, and sets the is_new_entry flag to True.
+ """
+ self.currently_selected_menu_item = models.MenuItems.new_empty()
+ self.banner_text = ""
+ self.is_new_entry = True
+
+ async def on_press_select_menu_item(
+ self, selected_menu_item: models.MenuItems
+ ) -> None:
+ """
+ Perform actions when a menu item is selected.
+
+ This method sets the currently selected menu item to the selected menu item,
+ which is passed as an argument.
+
+ Args:
+ selected_menu_item: The selected menu item.
+ """
+ self.currently_selected_menu_item = selected_menu_item
+
+ def build(self) -> rio.Component:
+ """
+ Builds the component to be rendered.
+
+ If there is no currently selected menu item, only the Banner and ItemList component is returned.
+
+ Otherwise, if there is a currently selected menu item, both the Banner and ItemList component
+ and the ItemEditor component are returned.
+
+ Returns:
+ rio.Component: The components to be rendered.
+ See the approx. layout below:
+
+ ############### Column ###############
+ # Banner #
+ # +------------------------------- #
+ # | ItemList | #
+ # | | #
+ # +------------------------------- #
+ ######################################
+
+ or
+
+ ############### Column ###############
+ # Banner #
+ # +------------------------------- #
+ # | ItemList | ItemEditor | #
+ # | | | #
+ # +------------------------------- #
+ ######################################
+
+ """
+
+ if self.currently_selected_menu_item is None:
+ return rio.Column(
+ rio.Banner(self.banner_text, style="danger"),
+ comps.ItemList(
+ menu_item_set=self.menu_item_set,
+ on_add_new_item_event=self.on_press_add_new_item,
+ on_delete_item_event=self.on_press_delete_item,
+ on_select_item_event=self.on_press_select_menu_item,
+ align_y=0,
+ ),
+ align_y=0,
+ margin=3,
+ )
+ else:
+ return rio.Column(
+ rio.Banner(self.banner_text, style="danger"),
+ rio.Row(
+ comps.ItemList(
+ menu_item_set=self.menu_item_set,
+ on_add_new_item_event=self.on_press_add_new_item,
+ on_delete_item_event=self.on_press_delete_item,
+ on_select_item_event=self.on_press_select_menu_item,
+ align_y=0,
+ ),
+ comps.ItemEditor(
+ self.currently_selected_menu_item,
+ new_entry=self.is_new_entry,
+ on_cancel_event=self.on_press_cancel_event,
+ on_save_event=self.on_press_save_event,
+ ),
+ spacing=1,
+ proportions=(2, 1),
+ ),
+ spacing=1,
+ align_y=0,
+ margin=3,
+ )
+
+
+#
diff --git a/rio/snippets/snippet-files/project-template-Simple CRUD/thumbnail.svg b/rio/snippets/snippet-files/project-template-Simple CRUD/thumbnail.svg
new file mode 100644
index 00000000..48a4248e
--- /dev/null
+++ b/rio/snippets/snippet-files/project-template-Simple CRUD/thumbnail.svg
@@ -0,0 +1,97 @@
+
+