diff --git a/intro/part3/README.md b/intro/part3/README.md
new file mode 100644
index 0000000..bd69f36
--- /dev/null
+++ b/intro/part3/README.md
@@ -0,0 +1,21 @@
+# Part 3
+
+This is just like last time, except first we'll install the `elm/browser` package so we can create an interactive app in the browser, instead of static HTML.
+
+To install the package, `cd` into the `part3/` directory and run:
+
+```shell
+elm install elm/browser
+```
+
+Then build everything the same way as last time:
+
+```shell
+elm make src/Main.elm --output elm.js
+```
+
+Finally, open `index.html` in your browser.
+
+## Exercise
+
+Open `src/Main.elm` in your editor and resolve the TODOs there.
diff --git a/intro/part3/elm.json b/intro/part3/elm.json
new file mode 100644
index 0000000..dd41cae
--- /dev/null
+++ b/intro/part3/elm.json
@@ -0,0 +1,24 @@
+{
+ "type": "application",
+ "source-directories": [
+ "src"
+ ],
+ "elm-version": "0.19.0",
+ "dependencies": {
+ "direct": {
+ "elm/browser": "1.0.0",
+ "elm/core": "1.0.0",
+ "elm/html": "1.0.0"
+ },
+ "indirect": {
+ "elm/json": "1.0.0",
+ "elm/time": "1.0.0",
+ "elm/url": "1.0.0",
+ "elm/virtual-dom": "1.0.0"
+ }
+ },
+ "test-dependencies": {
+ "direct": {},
+ "indirect": {}
+ }
+}
\ No newline at end of file
diff --git a/intro/part3/index.html b/intro/part3/index.html
new file mode 100644
index 0000000..3860a04
--- /dev/null
+++ b/intro/part3/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+ Elm Workshop
+
+
+
+
+
+
+
+
diff --git a/intro/part3/src/Article.elm b/intro/part3/src/Article.elm
new file mode 100644
index 0000000..833e956
--- /dev/null
+++ b/intro/part3/src/Article.elm
@@ -0,0 +1,21 @@
+module Article exposing (feed, tags)
+
+-- For now, this module only holds hardcoded data.
+--
+-- In future exercises, it will read data from the server!
+
+
+tags =
+ [ "elm"
+ , "fun"
+ , "programming"
+ , "dragons"
+ ]
+
+
+feed =
+ [ { title = "Elm is fun!", description = "Elm", body = "I've really been enjoying it!", tags = [ "elm", "fun" ], slug = "elm-is-fun--zb6nba" }
+ , { title = "Who says undefined isn't a function anyway?", description = "Functions", body = "Quite frankly I think undefined can be anything it wants to be,if it believes in itself.", slug = "who-says-undefined-isnt-a-function-anyway-t39ope", tags = [ "programming" ] }
+ , { title = "This compiler is pretty neat", description = "Elm", body = "It tells me about problems in my code. How neat is that?", tags = [ "compilers", "elm" ], slug = "this-compiler-is-pretty-neat-9ycui8" }
+ , { title = "Are dragons real?", description = "dragons", body = "Do Komodo Dragons count? I think they should. It's right there in the name!", tags = [ "dragons" ], slug = "are-dragons-real-467lsh" }
+ ]
diff --git a/intro/part3/src/Main.elm b/intro/part3/src/Main.elm
new file mode 100644
index 0000000..9b9d1c8
--- /dev/null
+++ b/intro/part3/src/Main.elm
@@ -0,0 +1,132 @@
+module Main exposing (main)
+
+-- 👇 You can see our new `Article` module in `src/Article.elm`
+
+import Article
+import Browser
+import Html exposing (..)
+import Html.Attributes exposing (..)
+import Html.Events exposing (onClick)
+
+
+-- MODEL
+
+
+initialModel =
+ { tags = Article.tags
+ , selectedTag = "elm"
+ , allArticles = Article.feed
+ }
+
+
+
+-- UPDATE
+
+
+update msg model =
+ {- 👉 TODO: If `msg.description` is "ClickedTag", then
+ set the model's `selectedTag` field to be `msg.data`
+
+ 💡 HINT 1: record update syntax looks like this:
+
+ { model | foo = bar }
+
+ 💡 HINT 2: Don't forget, every `if` must have an `else`!
+
+ -}
+ model
+
+
+
+-- VIEW
+
+
+view model =
+ let
+ {- 👉 TODO: Filter the articles down to onl the ones
+ that include the currently selected tag.
+
+ 💡 HINT: Replace `True` below with something involving
+ `List.member`, `article.tags`, and `model.selectedTag`
+
+ Docs for List.member: http://package.elm-lang.org/packages/elm-lang/core/latest/List#member
+ -}
+ articles =
+ List.filter (\article -> True)
+ model.allArticles
+
+ feed =
+ List.map viewArticle articles
+ in
+ div [ class "home-page" ]
+ [ viewBanner
+ , div [ class "container page" ]
+ [ div [ class "row" ]
+ [ div [ class "col-md-9" ] feed
+ , div [ class "col-md-3" ]
+ [ div [ class "sidebar" ]
+ [ p [] [ text "Popular Tags" ]
+ , viewTags model
+ ]
+ ]
+ ]
+ ]
+ ]
+
+
+viewArticle article =
+ div [ class "article-preview" ]
+ [ h1 [] [ text article.title ]
+ , p [] [ text article.description ]
+ , span [] [ text "Read more..." ]
+ ]
+
+
+viewBanner =
+ div [ class "banner" ]
+ [ div [ class "container" ]
+ [ h1 [ class "logo-font" ] [ text "conduit" ]
+ , p [] [ text "A place to share your knowledge." ]
+ ]
+ ]
+
+
+viewTag selectedTagName tagName =
+ let
+ otherClass =
+ if tagName == selectedTagName then
+ "tag-selected"
+ else
+ "tag-default"
+ in
+ button
+ [ class ("tag-pill " ++ otherClass)
+
+ {- 👉 TODO: Add an `onClick` handler which sends a msg
+ that our `update` function above will use
+ to set the currently selected tag to `tagName`.
+
+ 💡 HINT: It should look something like this:
+
+ , onClick { description = … , data = … }
+
+ 👆 Don't forget to add a comma before `onClick`!
+ -}
+ ]
+ [ text tagName ]
+
+
+viewTags model =
+ div [ class "tag-list" ] (List.map (viewTag model.selectedTag) model.tags)
+
+
+
+-- MAIN
+
+
+main =
+ Browser.sandbox
+ { init = initialModel
+ , view = view
+ , update = update
+ }