diff --git a/README.md b/README.md
index 3c5b4a6..ac67598 100644
--- a/README.md
+++ b/README.md
@@ -13,3 +13,4 @@ The experiments are currently organized based on the language they use:
 - [Idris](./idris/)
 - [Typst](./typst/)
 - [Python](./python/)
+- [Elm](./elm/)
diff --git a/elm/README.md b/elm/README.md
new file mode 100644
index 0000000..558568d
--- /dev/null
+++ b/elm/README.md
@@ -0,0 +1,5 @@
+# Elm
+
+| Name                    | Description                   |
+| ----------------------- | ----------------------------- |
+| [todolist](./todolist/) | Basic todolist implementation |
diff --git a/elm/todolist/.github/workflows/elm.yml b/elm/todolist/.github/workflows/elm.yml
new file mode 100644
index 0000000..ad95080
--- /dev/null
+++ b/elm/todolist/.github/workflows/elm.yml
@@ -0,0 +1,29 @@
+name: Elm
+
+on: [push]
+
+jobs:
+  deploy:
+    runs-on: ubuntu-latest
+
+    strategy:
+      matrix:
+        node-version: [12.x]
+
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v1
+      - name: Use node 12
+        uses: actions/setup-node@v1
+
+        with:
+          node-version: ${{ matrix.node-version }}
+      - run: npm i -g elm create-elm-app
+      - run: elm-app build
+      - name: Deploy to github pages
+        uses: JamesIves/github-pages-deploy-action@master
+        env:
+          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
+          BASE_BRANCH: master
+          BRANCH: gh-pages
+          FOLDER: build
diff --git a/elm/todolist/.gitignore b/elm/todolist/.gitignore
new file mode 100644
index 0000000..e7e7f12
--- /dev/null
+++ b/elm/todolist/.gitignore
@@ -0,0 +1,14 @@
+# Distribution
+build/
+
+# elm-package generated files
+elm-stuff
+
+# elm-repl generated files
+repl-temp-*
+
+# Dependency directories
+node_modules
+
+# Desktop Services Store on macOS
+.DS_Store
diff --git a/elm/todolist/README.md b/elm/todolist/README.md
new file mode 100644
index 0000000..3b963fc
--- /dev/null
+++ b/elm/todolist/README.md
@@ -0,0 +1,3 @@
+# Elm todo app
+
+Todo app made in 1h for getting a better understanding of the elm architecture
diff --git a/elm/todolist/elm-analyse.json b/elm/todolist/elm-analyse.json
new file mode 100644
index 0000000..2e716e1
--- /dev/null
+++ b/elm/todolist/elm-analyse.json
@@ -0,0 +1,6 @@
+{
+  "checks": {
+    "ExposeAll": false,
+    "ImportAll": false
+  }
+}
diff --git a/elm/todolist/elm.json b/elm/todolist/elm.json
new file mode 100644
index 0000000..5e90087
--- /dev/null
+++ b/elm/todolist/elm.json
@@ -0,0 +1,26 @@
+{
+    "type": "application",
+    "source-directories": ["src"],
+    "elm-version": "0.19.1",
+    "dependencies": {
+        "direct": {
+            "elm/browser": "1.0.1",
+            "elm/core": "1.0.2",
+            "elm/html": "1.0.0"
+        },
+        "indirect": {
+            "elm/json": "1.1.2",
+            "elm/time": "1.0.0",
+            "elm/url": "1.0.0",
+            "elm/virtual-dom": "1.0.2"
+        }
+    },
+    "test-dependencies": {
+        "direct": {
+            "elm-explorations/test": "1.0.0"
+        },
+        "indirect": {
+            "elm/random": "1.0.0"
+        }
+    }
+}
diff --git a/elm/todolist/elmapp.config.js b/elm/todolist/elmapp.config.js
new file mode 100644
index 0000000..ae3810d
--- /dev/null
+++ b/elm/todolist/elmapp.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+  homepage: "https://mateadrielrafael.github.io/elmTodolist"
+};
diff --git a/elm/todolist/public/favicon.ico b/elm/todolist/public/favicon.ico
new file mode 100644
index 0000000..d7057bd
Binary files /dev/null and b/elm/todolist/public/favicon.ico differ
diff --git a/elm/todolist/public/index.html b/elm/todolist/public/index.html
new file mode 100644
index 0000000..7bef8ed
--- /dev/null
+++ b/elm/todolist/public/index.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta http-equiv="x-ua-compatible" content="ie=edge" />
+    <meta
+      name="viewport"
+      content="width=device-width, initial-scale=1, shrink-to-fit=no"
+    />
+    <meta name="theme-color" content="#000000" />
+    <!--
+      manifest.json provides metadata used when your web app is added to the
+      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
+    -->
+    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
+    <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
+    <title>Elm App</title>
+  </head>
+  <body>
+    <noscript>
+      You need to enable JavaScript to run this app.
+    </noscript>
+    <div id="root"></div>
+  </body>
+</html>
diff --git a/elm/todolist/public/logo.svg b/elm/todolist/public/logo.svg
new file mode 100644
index 0000000..4321d4d
--- /dev/null
+++ b/elm/todolist/public/logo.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+   viewBox="0 0 323.141 322.95" enable-background="new 0 0 323.141 322.95" xml:space="preserve">
+<g>
+  <polygon
+    fill="#34495E"
+    points="161.649,152.782 231.514,82.916 91.783,82.916"/>
+
+  <polygon
+    fill="#34495E"
+    points="8.867,0 79.241,70.375 232.213,70.375 161.838,0"/>
+
+  <rect
+    fill="#34495E"
+    x="192.99"
+    y="107.392"
+    transform="matrix(0.7071 0.7071 -0.7071 0.7071 186.4727 -127.2386)"
+    width="107.676"
+    height="108.167"/>
+
+  <polygon
+    fill="#34495E"
+    points="323.298,143.724 323.298,0 179.573,0"/>
+
+  <polygon
+    fill="#34495E"
+    points="152.781,161.649 0,8.868 0,314.432"/>
+
+  <polygon
+    fill="#34495E"
+    points="255.522,246.655 323.298,314.432 323.298,178.879"/>
+
+  <polygon
+    fill="#34495E"
+    points="161.649,170.517 8.869,323.298 314.43,323.298"/>
+</g>
+</svg>
diff --git a/elm/todolist/public/manifest.json b/elm/todolist/public/manifest.json
new file mode 100644
index 0000000..9b7dc41
--- /dev/null
+++ b/elm/todolist/public/manifest.json
@@ -0,0 +1,15 @@
+{
+  "short_name": "Elm App",
+  "name": "Create Elm App Sample",
+  "icons": [
+    {
+      "src": "favicon.ico",
+      "sizes": "192x192",
+      "type": "image/png"
+    }
+  ],
+  "start_url": "./index.html",
+  "display": "standalone",
+  "theme_color": "#000000",
+  "background_color": "#ffffff"
+}
diff --git a/elm/todolist/src/Main.elm b/elm/todolist/src/Main.elm
new file mode 100644
index 0000000..8413e38
--- /dev/null
+++ b/elm/todolist/src/Main.elm
@@ -0,0 +1,167 @@
+module Main exposing (..)
+
+import Browser
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (..)
+
+
+
+-- HELPER TYPES
+
+
+type alias Todo =
+    { done : Bool
+    , text : String
+    , id : Int
+    }
+
+
+
+---- MODEL ----
+
+
+type alias Model =
+    { todos : List Todo
+    , lastId : Int
+    , nextTodoText : String
+    }
+
+
+init : Model
+init =
+    Model [] 0 ""
+
+
+
+---- UPDATE ----
+
+
+type Msg
+    = AddTodo
+    | DeleteTodo Int
+    | ToggleTodoCompletion Int
+    | SetNextTodoText String
+
+
+update : Msg -> Model -> Model
+update message model =
+    case message of
+        AddTodo ->
+            if model.nextTodoText == "" then
+                model
+
+            else
+                let
+                    id =
+                        model.lastId + 1
+
+                    text =
+                        model.nextTodoText
+                in
+                { model | lastId = id, todos = Todo False text id :: model.todos }
+
+        DeleteTodo id ->
+            { model | todos = List.filter ((/=) id << .id) model.todos }
+
+        ToggleTodoCompletion id ->
+            { model
+                | todos =
+                    List.map
+                        (\todo ->
+                            if todo.id == id then
+                                { todo | done = not todo.done }
+
+                            else
+                                todo
+                        )
+                        model.todos
+            }
+
+        SetNextTodoText text ->
+            { model | nextTodoText = text }
+
+
+
+---- VIEW ----
+
+
+view : Model -> Html Msg
+view model =
+    div [ class "container" ]
+        [ header model
+        , div [ class "todos" ] <|
+            List.map
+                todoView
+                model.todos
+        ]
+
+
+
+-- HEADER
+
+
+header : Model -> Html Msg
+header _ =
+    div [ class "header" ]
+        [ button
+            [ class "btn"
+            , onClick AddTodo
+            ]
+            [ text "add todo" ]
+        , input
+            [ placeholder "todo text"
+            , onInput SetNextTodoText
+            , class "todoTextInput"
+            ]
+            []
+        ]
+
+
+
+-- _TODO VIEW
+
+
+todoClasses : Todo -> String
+todoClasses todo =
+    "todo"
+        ++ (if todo.done then
+                " completed"
+
+            else
+                ""
+           )
+
+
+todoView : Todo -> Html Msg
+todoView todo =
+    div [ class <| todoClasses todo ]
+        [ div [ class "todoCompleted" ]
+            [ input
+                [ checked todo.done
+                , type_ "checkbox"
+                , class "todoCheckbox"
+                , onCheck <| \_ -> ToggleTodoCompletion todo.id
+                ]
+                []
+            ]
+        , div [ class "todoText" ] [ text todo.text ]
+        , button
+            [ class "btn"
+            , onClick <| DeleteTodo todo.id
+            ]
+            [ text "Delete todo" ]
+        ]
+
+
+
+---- PROGRAM ----
+
+
+main : Program () Model Msg
+main =
+    Browser.sandbox
+        { view = view
+        , init = init
+        , update = update
+        }
diff --git a/elm/todolist/src/index.js b/elm/todolist/src/index.js
new file mode 100644
index 0000000..c9bbd20
--- /dev/null
+++ b/elm/todolist/src/index.js
@@ -0,0 +1,12 @@
+import './main.css';
+import { Elm } from './Main.elm';
+import * as serviceWorker from './serviceWorker';
+
+Elm.Main.init({
+  node: document.getElementById('root')
+});
+
+// If you want your app to work offline and load faster, you can change
+// unregister() to register() below. Note this comes with some pitfalls.
+// Learn more about service workers: https://bit.ly/CRA-PWA
+serviceWorker.unregister();
diff --git a/elm/todolist/src/main.css b/elm/todolist/src/main.css
new file mode 100644
index 0000000..206b7b0
--- /dev/null
+++ b/elm/todolist/src/main.css
@@ -0,0 +1,111 @@
+/*
+  elm-hot creates an additional div wrapper around the app to make HMR possible.
+  This could break styling in development mode if you are using Elm UI.
+
+  More context in the issue:
+    https://github.com/halfzebra/create-elm-app/issues/320
+*/
+[data-elm-hot="true"] {
+  height: inherit;
+}
+
+:root {
+  --secondary: #009fb7;
+  --primary: #1a213b;
+  --on-secondary: #eff1f4;
+  --disabled: #555555;
+}
+
+body {
+  font-family: "Source Sans Pro", "Trebuchet MS", "Lucida Grande",
+    "Bitstream Vera Sans", "Helvetica Neue", sans-serif;
+  margin: 0;
+  background: var(--primary);
+
+  color: var(--on-secondary);
+}
+
+h1 {
+  font-size: 30px;
+}
+
+.header {
+  display: flex;
+}
+
+.btn {
+  background-color: var(--secondary);
+  color: var(--on-secondary);
+  padding: 1rem;
+  margin: 1rem;
+  border: none;
+}
+
+input {
+  outline: none;
+}
+
+.todos {
+  display: flex;
+  flex-direction: column;
+  min-height: 67vh;
+  border: 1px solid var(--secondary);
+}
+
+.todo {
+  transition: filter 0.5s;
+  background: var(--primary);
+  display: flex;
+}
+
+.todo:hover {
+  filter: brightness(1.4);
+}
+
+.todo.completed {
+  color: var(--disabled);
+
+  text-decoration: line-through;
+}
+
+.todo.completed > .btn {
+  color: var(--disabled);
+}
+
+.todo > * {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+}
+
+.todoText {
+  justify-content: flex-start;
+  flex-direction: row;
+  flex-grow: 1;
+}
+
+.todoCompleted {
+  margin: 1rem;
+}
+
+.todoTextInput {
+  background: var(--primary);
+  border: 1px var(--secondary) solid;
+  margin: 1rem;
+  padding: 2px;
+  box-sizing: border-box;
+  color: var(--on-secondary);
+  font-size: 1.5rem;
+  transition: filter 0.5s;
+}
+
+.todoTextInput:focus {
+  filter: brightness(1.4);
+}
+
+.todoCheckbox {
+  width: 1rem;
+  height: 1rem;
+  background: var(--secondary);
+}
diff --git a/elm/todolist/src/serviceWorker.js b/elm/todolist/src/serviceWorker.js
new file mode 100644
index 0000000..f8c7e50
--- /dev/null
+++ b/elm/todolist/src/serviceWorker.js
@@ -0,0 +1,135 @@
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read https://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+  window.location.hostname === 'localhost' ||
+    // [::1] is the IPv6 localhost address.
+    window.location.hostname === '[::1]' ||
+    // 127.0.0.1/8 is considered localhost for IPv4.
+    window.location.hostname.match(
+      /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
+    )
+);
+
+export function register(config) {
+  if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+    // The URL constructor is available in all browsers that support SW.
+    const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+    if (publicUrl.origin !== window.location.origin) {
+      // Our service worker won't work if PUBLIC_URL is on a different origin
+      // from what our page is served on. This might happen if a CDN is used to
+      // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+      return;
+    }
+
+    window.addEventListener('load', () => {
+      const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+      if (isLocalhost) {
+        // This is running on localhost. Let's check if a service worker still exists or not.
+        checkValidServiceWorker(swUrl, config);
+
+        // Add some additional logging to localhost, pointing developers to the
+        // service worker/PWA documentation.
+        navigator.serviceWorker.ready.then(() => {
+          console.log(
+            'This web app is being served cache-first by a service ' +
+              'worker. To learn more, visit https://bit.ly/CRA-PWA'
+          );
+        });
+      } else {
+        // Is not localhost. Just register service worker
+        registerValidSW(swUrl, config);
+      }
+    });
+  }
+}
+
+function registerValidSW(swUrl, config) {
+  navigator.serviceWorker
+    .register(swUrl)
+    .then(registration => {
+      registration.onupdatefound = () => {
+        const installingWorker = registration.installing;
+        if (installingWorker == null) {
+          return;
+        }
+        installingWorker.onstatechange = () => {
+          if (installingWorker.state === 'installed') {
+            if (navigator.serviceWorker.controller) {
+              // At this point, the updated precached content has been fetched,
+              // but the previous service worker will still serve the older
+              // content until all client tabs are closed.
+              console.log(
+                'New content is available and will be used when all ' +
+                  'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
+              );
+
+              // Execute callback
+              if (config && config.onUpdate) {
+                config.onUpdate(registration);
+              }
+            } else {
+              // At this point, everything has been precached.
+              // It's the perfect time to display a
+              // "Content is cached for offline use." message.
+              console.log('Content is cached for offline use.');
+
+              // Execute callback
+              if (config && config.onSuccess) {
+                config.onSuccess(registration);
+              }
+            }
+          }
+        };
+      };
+    })
+    .catch(error => {
+      console.error('Error during service worker registration:', error);
+    });
+}
+
+function checkValidServiceWorker(swUrl, config) {
+  // Check if the service worker can be found. If it can't reload the page.
+  fetch(swUrl)
+    .then(response => {
+      // Ensure service worker exists, and that we really are getting a JS file.
+      const contentType = response.headers.get('content-type');
+      if (
+        response.status === 404 ||
+        (contentType != null && contentType.indexOf('javascript') === -1)
+      ) {
+        // No service worker found. Probably a different app. Reload the page.
+        navigator.serviceWorker.ready.then(registration => {
+          registration.unregister().then(() => {
+            window.location.reload();
+          });
+        });
+      } else {
+        // Service worker found. Proceed as normal.
+        registerValidSW(swUrl, config);
+      }
+    })
+    .catch(() => {
+      console.log(
+        'No internet connection found. App is running in offline mode.'
+      );
+    });
+}
+
+export function unregister() {
+  if ('serviceWorker' in navigator) {
+    navigator.serviceWorker.ready.then(registration => {
+      registration.unregister();
+    });
+  }
+}