diff --git a/common/dev/user.clj b/common/dev/user.clj
index 3cb650922..c558def7b 100644
--- a/common/dev/user.clj
+++ b/common/dev/user.clj
@@ -6,6 +6,8 @@
 
 (ns user
   (:require
+   [app.common.data :as d]
+   [app.common.fressian :as fres]
    [app.common.pprint :as pp]
    [app.common.schema :as sm]
    [app.common.schema.desc-js-like :as smdj]
diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc
index ecf55b115..c0086b73c 100644
--- a/common/src/app/common/data.cljc
+++ b/common/src/app/common/data.cljc
@@ -44,8 +44,8 @@
 
 (defn ordered-map
   ([] lkm/empty-linked-map)
-  ([a] (conj lkm/empty-linked-map a))
-  ([a & xs] (apply conj lkm/empty-linked-map a xs)))
+  ([k a] (assoc lkm/empty-linked-map k a))
+  ([k a & xs] (apply assoc lkm/empty-linked-map k a xs)))
 
 (defn ordered-set?
   [o]
diff --git a/common/src/app/common/files/changes.cljc b/common/src/app/common/files/changes.cljc
index d8c05619a..dacaf7f09 100644
--- a/common/src/app/common/files/changes.cljc
+++ b/common/src/app/common/files/changes.cljc
@@ -25,6 +25,7 @@
    [app.common.types.shape-tree :as ctst]
    [app.common.types.token :as cto]
    [app.common.types.token-theme :as ctot]
+   [app.common.types.tokens-lib :as ctob]
    [app.common.types.tokens-list :as ctol]
    [app.common.types.tokens-theme-list :as ctotl]
    [app.common.types.typographies-list :as ctyl]
@@ -289,28 +290,36 @@
      [:map {:title "ModTokenSetChange"}
       [:type [:= :mod-token-set]]
       [:id ::sm/uuid]
+      [:name :string]
       [:token-set ::ctot/token-set]]]
 
     [:del-token-set
      [:map {:title "DelTokenSetChange"}
       [:type [:= :del-token-set]]
-      [:id ::sm/uuid]]]
+      [:id ::sm/uuid]
+      [:name :string]]]
 
     [:add-token
      [:map {:title "AddTokenChange"}
       [:type [:= :add-token]]
+      [:set-id ::sm/uuid]
+      [:set-name :string]
       [:token ::cto/token]]]
 
     [:mod-token
      [:map {:title "ModTokenChange"}
       [:type [:= :mod-token]]
+      [:set-id ::sm/uuid]
+      [:set-name :string]
       [:id ::sm/uuid]
       [:token ::cto/token]]]
 
     [:del-token
      [:map {:title "DelTokenChange"}
       [:type [:= :del-token]]
-      [:id ::sm/uuid]]]]])
+      [:set-name :string]
+      [:id ::sm/uuid]
+      [:name :string]]]]])
 
 (sm/register! ::changes
   [:sequential {:gen/max 2} ::change])
@@ -780,16 +789,37 @@
 ;; -- Tokens
 
 (defmethod process-change :add-token
-  [data {:keys [token]}]
-  (ctol/add-token data token))
+  [data {:keys [set-id set-name token]}]
+  (-> data
+      (ctol/add-token set-id token)
+      (update :tokens-lib
+              #(-> %
+                   (ctob/ensure-tokens-lib)
+                   (ctob/add-token-in-set set-name (ctob/make-token token))))))
 
 (defmethod process-change :mod-token
-  [data {:keys [id token]}]
-  (ctol/update-token data id merge token))
+  [data {:keys [set-id set-name id token]}]
+  (-> data
+      (ctol/update-token data set-id id merge token)
+      (update :tokens-lib
+              #(-> %
+                   (ctob/ensure-tokens-lib)
+                   (ctob/update-token-in-set
+                    set-name
+                    (:name token)
+                    (fn [old-token]
+                      (ctob/make-token (merge old-token token))))))))
 
 (defmethod process-change :del-token
-  [data {:keys [id]}]
-  (ctol/delete-token data id))
+  [data {:keys [set-name id name]}]
+  (-> data
+      (ctol/delete-token id)
+      (update :tokens-lib
+              #(-> %
+                   (ctob/ensure-tokens-lib)
+                   (ctob/delete-token-from-set
+                    set-name
+                    name)))))
 
 (defmethod process-change :add-temporary-token-theme
   [data {:keys [token-theme]}]
@@ -817,15 +847,32 @@
 
 (defmethod process-change :add-token-set
   [data {:keys [token-set]}]
-  (ctotl/add-token-set data token-set))
+  (-> data
+      (ctotl/add-token-set token-set)
+      (update :tokens-lib
+              #(-> %
+                   (ctob/ensure-tokens-lib)
+                   (ctob/add-set (ctob/make-token-set token-set))))))
 
 (defmethod process-change :mod-token-set
-  [data {:keys [id token-set]}]
-  (ctotl/update-token-set data id merge token-set))
+  [data {:keys [id name token-set]}]
+  (-> data
+      (ctotl/update-token-set id merge token-set)
+      (update :tokens-lib
+              #(-> %
+                   (ctob/ensure-tokens-lib)
+                   (ctob/update-set name (fn [prev-set]
+                                           (merge prev-set
+                                                  (dissoc token-set :tokens))))))))
 
 (defmethod process-change :del-token-set
-  [data {:keys [id]}]
-  (ctotl/delete-token-set data id))
+  [data {:keys [id name]}]
+  (-> data
+      (ctotl/delete-token-set id)
+      (update :tokens-lib
+              #(-> %
+                   (ctob/ensure-tokens-lib)
+                   (ctob/delete-set name)))))
 
 ;; === Operations
 (defmethod process-operation :set
@@ -980,5 +1027,3 @@
 (defmethod frames-changed :default
   [_ _]
   nil)
-
-
diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc
index ed779cb5c..c0b64107c 100644
--- a/common/src/app/common/files/changes_builder.cljc
+++ b/common/src/app/common/files/changes_builder.cljc
@@ -737,48 +737,48 @@
   [changes token-set]
   (-> changes
       (update :redo-changes conj {:type :add-token-set :token-set token-set})
-      (update :undo-changes conj {:type :del-token-set :id (:id token-set)})
+      (update :undo-changes conj {:type :del-token-set :id (:id token-set) :name (:name token-set)})
       (apply-changes-local)))
 
 (defn update-token-set
   [changes token-set prev-token-set]
   (-> changes
-      (update :redo-changes conj {:type :mod-token-set :id (:id token-set) :token-set token-set})
-      (update :undo-changes conj {:type :mod-token-set :id (:id token-set) :token-set (or prev-token-set token-set)})
+      (update :redo-changes conj {:type :mod-token-set :id (:id token-set) :name (:name prev-token-set) :token-set token-set})
+      (update :undo-changes conj {:type :mod-token-set :id (:id token-set) :name (:name prev-token-set) :token-set (or prev-token-set token-set)})
       (apply-changes-local)))
 
 (defn delete-token-set
-  [changes token-set-id]
+  [changes token-set-id token-set-name]
   (assert-library! changes)
   (let [library-data (::library-data (meta changes))
         prev-token-set (get-in library-data [:token-sets-index token-set-id])]
     (-> changes
-        (update :redo-changes conj {:type :del-token-set :id token-set-id})
+        (update :redo-changes conj {:type :del-token-set :id token-set-id :name token-set-name})
         (update :undo-changes conj {:type :add-token-set :token-set prev-token-set})
         (apply-changes-local))))
 
 (defn add-token
-  [changes token]
+  [changes set-id set-name token]
   (-> changes
-      (update :redo-changes conj {:type :add-token :token token})
-      (update :undo-changes conj {:type :del-token :id (:id token)})
+      (update :redo-changes conj {:type :add-token :set-id set-id :set-name set-name :token token})
+      (update :undo-changes conj {:type :del-token :set-name set-name :id (:id token) :name (:name token)})
       (apply-changes-local)))
 
 (defn update-token
-  [changes {:keys [id] :as token} prev-token]
+  [changes set-id set-name {:keys [id] :as token} prev-token]
   (-> changes
-      (update :redo-changes conj {:type :mod-token :id id :token token})
-      (update :undo-changes conj {:type :mod-token :id id :token (or prev-token token)})
+      (update :redo-changes conj {:type :mod-token :set-id set-id :set-name set-name :id id :token token})
+      (update :undo-changes conj {:type :mod-token :set-id set-id :set-name set-name :id id :token (or prev-token token)})
       (apply-changes-local)))
 
 (defn delete-token
-  [changes token-id]
+  [changes set-name token-id token-name]
   (assert-library! changes)
   (let [library-data (::library-data (meta changes))
         prev-token (get-in library-data [:tokens token-id])]
     (-> changes
-        (update :redo-changes conj {:type :del-token :id token-id})
-        (update :undo-changes conj {:type :add-token :token prev-token})
+        (update :redo-changes conj {:type :del-token :set-name set-name :id token-id :name token-name})
+        (update :undo-changes conj {:type :add-token :set-id uuid/zero :set-name set-name :token prev-token})
         (apply-changes-local))))
 
 (defn add-component
diff --git a/common/src/app/common/fressian.clj b/common/src/app/common/fressian.clj
index 282620698..4a640cd8c 100644
--- a/common/src/app/common/fressian.clj
+++ b/common/src/app/common/fressian.clj
@@ -15,6 +15,7 @@
    java.time.Instant
    java.time.OffsetDateTime
    java.util.List
+   linked.map.LinkedMap
    org.fressian.Reader
    org.fressian.StreamingWriter
    org.fressian.Writer
@@ -109,6 +110,13 @@
       (clojure.lang.PersistentArrayMap. (.toArray kvs))
       (clojure.lang.PersistentHashMap/create (seq kvs)))))
 
+(defn read-ordered-map
+  [^Reader rdr]
+  (let [kvs ^java.util.List (read-object! rdr)]
+    (reduce #(assoc %1 (first %2) (second %2))
+            (d/ordered-map)
+            (partition-all 2 (seq kvs)))))
+
 (def ^:dynamic *write-handler-lookup* nil)
 (def ^:dynamic *read-handler-lookup* nil)
 
@@ -225,6 +233,11 @@
   :wfn write-map-like
   :rfn read-map-like}
 
+ {:name "linked/map"
+  :class LinkedMap
+  :wfn write-map-like
+  :rfn read-ordered-map}
+
  {:name "clj/keyword"
   :class clojure.lang.Keyword
   :wfn write-named
diff --git a/common/src/app/common/schema.cljc b/common/src/app/common/schema.cljc
index 570cfa062..691da5f93 100644
--- a/common/src/app/common/schema.cljc
+++ b/common/src/app/common/schema.cljc
@@ -308,7 +308,6 @@
                                 ::explain explain}))))
       true)))
 
-
 (defn fast-validate!
   "A fast path for validation process, assumes the ILazySchema protocol
   implemented on the provided `s` schema. Sould not be used directly."
diff --git a/common/src/app/common/time.cljc b/common/src/app/common/time.cljc
index 6cd8601d6..8cbfe9541 100644
--- a/common/src/app/common/time.cljc
+++ b/common/src/app/common/time.cljc
@@ -26,11 +26,18 @@
   #?(:clj (Instant/now)
      :cljs (.local ^js DateTime)))
 
+#?(:clj
+   (defn is-after?
+     [one other]
+     (.isAfter one other)))
+
 (defn instant
   [s]
   #?(:clj  (Instant/ofEpochMilli s)
      :cljs (.fromMillis ^js DateTime s #js {:zone "local" :setZone false})))
 
+;; To check for valid date time we can just use the core inst? function
+
 #?(:cljs
    (extend-protocol IComparable
      DateTime
@@ -45,7 +52,6 @@
          0
          (if (< (inst-ms it) (inst-ms other)) -1 1)))))
 
-
 #?(:cljs
    (extend-type DateTime
      cljs.core/IEquiv
diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc
index 93ab8d4b2..21673bdb4 100644
--- a/common/src/app/common/transit.cljc
+++ b/common/src/app/common/transit.cljc
@@ -12,7 +12,7 @@
    [app.common.uri :as uri]
    [cognitect.transit :as t]
    [lambdaisland.uri :as luri]
-   [linked.core :as lk]
+   [linked.map :as lkm]
    [linked.set :as lks])
   #?(:clj
      (:import
@@ -24,6 +24,7 @@
       java.time.Instant
       java.time.OffsetDateTime
       lambdaisland.uri.URI
+      linked.map.LinkedMap
       linked.set.LinkedSet)))
 
 (def write-handlers (atom nil))
@@ -118,10 +119,15 @@
     {:id "u"
      :rfn parse-uuid})
 
+ {:id "ordered-map"
+  :class #?(:clj LinkedMap :cljs lkm/LinkedMap)
+  :wfn vec
+  :rfn #(into lkm/empty-linked-map %)}
+
  {:id "ordered-set"
   :class #?(:clj LinkedSet :cljs lks/LinkedSet)
   :wfn vec
-  :rfn #(into (lk/set) %)}
+  :rfn #(into lks/empty-linked-set %)}
 
  {:id "duration"
   :class #?(:clj Duration :cljs lxn/Duration)
diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc
index 593ec5d2d..dcf9900f8 100644
--- a/common/src/app/common/types/file.cljc
+++ b/common/src/app/common/types/file.cljc
@@ -28,6 +28,7 @@
    [app.common.types.shape-tree :as ctst]
    [app.common.types.token :as cto]
    [app.common.types.token-theme :as ctt]
+   [app.common.types.tokens-lib :as ctl]
    [app.common.types.typographies-list :as ctyl]
    [app.common.types.typography :as cty]
    [app.common.uuid :as uuid]
@@ -78,7 +79,8 @@
    [:token-sets-index {:optional true}
     [:map-of {:gen/max 10} ::sm/uuid ::ctt/token-set]]
    [:tokens {:optional true}
-    [:map-of {:gen/max 100} ::sm/uuid ::cto/token]]])
+    [:map-of {:gen/max 100} ::sm/uuid ::cto/token]]
+   [:tokens-lib {:optional true} ::ctl/tokens-lib]])
 
 (def check-file-data!
   (sm/check-fn ::data))
diff --git a/common/src/app/common/types/token.cljc b/common/src/app/common/types/token.cljc
index 104948105..6de8f9c29 100644
--- a/common/src/app/common/types/token.cljc
+++ b/common/src/app/common/types/token.cljc
@@ -47,8 +47,16 @@
     :string
     :typography})
 
+(defn valid-token-type?
+  [t]
+  (token-types t))
+
 (def token-name-ref :string)
 
+(defn valid-token-name-ref?
+  [n]
+  (string? n))
+
 (sm/register! ::token
   [:map {:title "Token"}
    [:id ::sm/uuid]
diff --git a/common/src/app/common/types/tokens_lib.cljc b/common/src/app/common/types/tokens_lib.cljc
new file mode 100644
index 000000000..55fb52a51
--- /dev/null
+++ b/common/src/app/common/types/tokens_lib.cljc
@@ -0,0 +1,319 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns app.common.types.tokens-lib
+  (:require
+   [app.common.data :as d]
+   [app.common.data.macros :as dm]
+   [app.common.schema :as sm]
+   [app.common.time :as dt]
+   [app.common.transit :as t]
+   [app.common.types.token :as cto]
+   #?(:clj [app.common.fressian :as fres])))
+
+;; #?(:clj (set! *warn-on-reflection* true))
+
+;; === Token
+
+(defrecord Token [name type value description modified-at])
+
+(def schema:token
+  [:and
+   [:map {:title "Token"}
+    [:name cto/token-name-ref]                            ;; not necessary to have uuid
+    [:type [::sm/one-of cto/token-types]]
+    [:value :any]
+    [:description [:maybe :string]]      ;; defrecord always have the attributes, even with nil value
+    [:modified-at ::sm/inst]]
+   [:fn (partial instance? Token)]])
+
+(sm/register! ::token schema:token)
+
+(def valid-token?
+  (sm/validator schema:token))
+
+(def check-token!
+  (sm/check-fn ::token))
+
+(defn make-token
+  [& {:keys [] :as params}]
+  (let [params (-> params
+                   (dissoc :id) ;; we will remove this when old data structures are removed
+                   (update :modified-at #(or % (dt/now))))
+        token  (map->Token params)]
+
+    (dm/assert!
+     "expected valid token"
+     (check-token! token))
+
+    token))
+
+;; === Token Set
+
+(defprotocol ITokenSet
+  (add-token [_ token] "add a token at the end of the list")
+  (update-token [_ token-name f] "update a token in the list")
+  (delete-token [_ token-name] "delete a token from the list"))
+
+(defrecord TokenSet [name description modified-at tokens]
+  ITokenSet
+  (add-token [_ token]
+    (dm/assert! "expected valid token" (check-token! token))
+    (TokenSet. name
+               description
+               (dt/now)
+               (assoc tokens (:name token) token)))
+
+  (update-token [this token-name f]
+    (if-let [token (get tokens token-name)]
+      (let [token' (-> (make-token (f token))
+                       (assoc :modified-at (dt/now)))]
+        (check-token! token')
+        (TokenSet. name
+                   description
+                   (dt/now)
+                   (cond-> tokens
+                      (not= (:name token) (:name token'))
+                      (dissoc (:name token))
+
+                      :always                          ; TODO: if token is renamed,
+                      (assoc (:name token') token')))) ;       it sould remain in
+      this))                                           ;       the same position
+
+  (delete-token [_ token-name]
+    (TokenSet. name
+               description
+               (dt/now)
+               (dissoc tokens token-name))))
+
+(def schema:token-set
+  [:and [:map {:title "TokenSet"}
+         [:name :string]
+         [:description [:maybe :string]]
+         [:modified-at ::sm/inst]
+         [:tokens [:map-of {:gen/max 5} :string ::token]]]
+   [:fn (partial instance? TokenSet)]])
+
+(sm/register! ::token-set schema:token-set)
+
+(def valid-token-set?
+  (sm/validator schema:token-set))
+
+(def check-token-set!
+  (sm/check-fn ::token-set))
+
+(defn make-token-set
+  [& {:keys [] :as params}]
+  (let [params    (-> params
+                      (dissoc :id)
+                      (update :modified-at #(or % (dt/now)))
+                      (update :tokens #(into (d/ordered-map) %)))
+        token-set (map->TokenSet params)]
+
+    (dm/assert!
+     "expected valid token set"
+     (check-token-set! token-set))
+
+    token-set))
+
+;; === TokenSets (collection)
+
+(defprotocol ITokenSets
+  (add-set [_ token-set] "add a set to the library, at the end")
+  (update-set [_ set-name f] "modify a set in the ilbrary")
+  (delete-set [_ set-name] "delete a set in the library")
+  (set-count [_] "get the total number if sets in the library")
+  (get-sets [_] "get an ordered sequence of all sets in the library")
+  (get-set [_ set-name] "get one set looking for name")
+  (validate [_]))
+
+(def schema:token-sets
+  [:and
+   [:map-of {:title "TokenSets"}
+    :string ::token-set]
+   [:fn d/ordered-map?]])
+
+(sm/register! ::token-sets schema:token-sets)
+
+(def valid-token-sets?
+  (sm/validator schema:token-sets))
+
+(def check-token-sets!
+  (sm/check-fn ::token-sets))
+
+;; === TokenThemes (collection)
+
+(def valid-token-themes?
+  (constantly true))
+
+;; === Tokens Lib
+
+(defprotocol ITokensLib
+  "A library of tokens, sets and themes."
+  (add-token-in-set [_ set-name token] "add token to a set")
+  (update-token-in-set [_ set-name token-name f] "update a token in a set")
+  (delete-token-from-set [_ set-name token-name] "delete a token from a set"))
+
+(deftype TokensLib [sets themes]
+  ;; NOTE: This is only for debug purposes, pending to properly
+  ;; implement the toString and alternative printing.
+  #?@(:clj  [clojure.lang.IDeref
+             (deref [_] {:sets sets :themes themes})]
+      :cljs [cljs.core/IDeref
+             (-deref [_] {:sets sets :themes themes})])
+
+  #?@(:cljs [cljs.core/IEncodeJS
+             (-clj->js [_] (js-obj "sets" (clj->js sets)
+                                   "themes" (clj->js themes)))])
+
+  ITokenSets
+  (add-set [_ token-set]
+    (dm/assert! "expected valid token set" (check-token-set! token-set))
+    (TokensLib. (assoc sets (:name token-set) token-set)
+                themes))
+
+  (update-set [this set-name f]
+    (if-let [set (get sets set-name)]
+      (let [set' (-> (make-token-set (f set))
+                     (assoc :modified-at (dt/now)))]
+        (check-token-set! set')
+        (TokensLib. (cond-> sets
+                      (not= (:name set) (:name set'))
+                      (dissoc (:name set))
+
+                      :always                    ; TODO: if set is renamed,
+                      (assoc (:name set') set')) ;       it sould remain in
+                    themes))                     ;       the same position
+      this))
+
+  (delete-set [_ set-name]
+    (TokensLib. (dissoc sets set-name)
+                themes))
+
+  (validate [_]
+    (and (valid-token-sets? sets)
+         (valid-token-themes? themes)))
+
+  (set-count [_]
+    (count sets))
+
+  (get-sets [_]
+    (vals sets))
+
+  (get-set [_ set-name]
+    (get sets set-name))
+
+  ITokensLib
+  (add-token-in-set [this set-name token]
+    (dm/assert! "expected valid token instance" (check-token! token))
+    (if (contains? sets set-name)
+      (TokensLib. (update sets set-name add-token token)
+                  themes)
+      this))
+
+  (update-token-in-set [this set-name token-name f]
+    (if (contains? sets set-name)
+      (TokensLib. (update sets set-name
+                          #(update-token % token-name f))
+                  themes)
+      this))
+  
+  (delete-token-from-set [this set-name token-name]
+    (if (contains? sets set-name)
+      (TokensLib. (update sets set-name
+                          #(delete-token % token-name))
+                  themes)
+      this)))
+
+(defn valid-tokens-lib?
+  [o]
+  (and (instance? TokensLib o)
+       (validate o)))
+
+(defn check-tokens-lib!
+  [lib]
+  (dm/assert!
+   "expected valid tokens lib"
+   (valid-tokens-lib? lib)))
+
+(defn make-tokens-lib
+  "Create an empty or prepopulated tokens library."
+  ([]
+   ;; NOTE: is possible that ordered map is not the most apropriate
+   ;; data structure and maybe we need a specific that allows us an
+   ;; easy way to reorder it, or just store inside Tokens data
+   ;; structure the data and the order separately as we already do
+   ;; with pages and pages-index.
+   (make-tokens-lib :sets (d/ordered-map)
+                    :themes (d/ordered-map)))
+
+  ([& {:keys [sets themes]}]
+   (let [tokens-lib (TokensLib. sets themes)]
+
+     (dm/assert!
+      "expected valid tokens lib"
+      (valid-tokens-lib? tokens-lib))
+
+     tokens-lib)))
+
+(defn ensure-tokens-lib
+  [tokens-lib]
+  (or tokens-lib (make-tokens-lib)))
+
+(def type:tokens-lib
+  {:type ::tokens-lib
+   :pred valid-tokens-lib?})
+
+(sm/register! ::tokens-lib type:tokens-lib)
+
+;; === Serialization handlers for RPC API and database
+
+(t/add-handlers!
+ {:id "penpot/tokens-lib"
+  :class TokensLib
+  :wfn deref
+  :rfn #(make-tokens-lib %)}
+ 
+ {:id "penpot/token-set"
+  :class TokenSet
+  :wfn #(into {} %)
+  :rfn #(make-token-set %)} 
+
+ {:id "penpot/token"
+  :class Token
+  :wfn #(into {} %)
+  :rfn #(make-token %)})
+
+#?(:clj
+   (fres/add-handlers!
+    {:name "penpot/token/v1"
+     :class Token
+     :wfn (fn [n w o]
+            (fres/write-tag! w n 1)
+            (fres/write-object! w (into {} o)))
+     :rfn (fn [r]
+            (let [obj (fres/read-object! r)]
+              (map->Token obj)))}
+
+    {:name "penpot/token-set/v1"
+     :class TokenSet
+     :wfn (fn [n w o]
+            (fres/write-tag! w n 1)
+            (fres/write-object! w (into {} o)))
+     :rfn (fn [r]
+            (let [obj (fres/read-object! r)]
+              (map->TokenSet obj)))}
+
+    {:name "penpot/tokens-lib/v1"
+     :class TokensLib
+     :wfn (fn [n w o]
+            (fres/write-tag! w n 2)
+            (fres/write-object! w (.-sets o))
+            (fres/write-object! w (.-themes o)))
+     :rfn (fn [r]
+            (let [sets   (fres/read-object! r)
+                  themes (fres/read-object! r)]
+              (->TokensLib sets themes)))}))
diff --git a/common/src/app/common/types/tokens_list.cljc b/common/src/app/common/types/tokens_list.cljc
index 58479f72e..b31262d4d 100644
--- a/common/src/app/common/types/tokens_list.cljc
+++ b/common/src/app/common/types/tokens_list.cljc
@@ -21,8 +21,12 @@
 
 (defn add-token
   "Adds a new token to the file data, setting its `modified-at` timestamp."
-  [file-data token]
-  (update file-data :tokens assoc (:id token) (touch token)))
+  [file-data token-set-id token]
+  (-> file-data
+      (update :tokens assoc (:id token) (touch token))
+      (d/update-in-when [:token-sets-index token-set-id] #(->
+                                                           (update % :tokens conj (:id token))
+                                                           (touch)))))
 
 (defn get-token
   "Retrieves a token by its ID from the file data."
diff --git a/common/test/common_tests/types/tokens_lib_test.cljc b/common/test/common_tests/types/tokens_lib_test.cljc
new file mode 100644
index 000000000..a57d10e5f
--- /dev/null
+++ b/common/test/common_tests/types/tokens_lib_test.cljc
@@ -0,0 +1,222 @@
+;; This Source Code Form is subject to the terms of the Mozilla Public
+;; License, v. 2.0. If a copy of the MPL was not distributed with this
+;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
+;;
+;; Copyright (c) KALEIDOS INC
+
+(ns common-tests.types.tokens-lib-test
+  (:require
+   [app.common.fressian :as fres]
+   [app.common.time :as dt]
+   [app.common.transit :as tr]
+   [app.common.types.tokens-lib :as ctob]
+   [clojure.test :as t]))
+
+(t/deftest make-token
+  (let [now    (dt/now)
+        token1 (ctob/make-token :name "test-token-1"
+                                :type :boolean
+                                :value true)
+        token2 (ctob/make-token :name "test-token-2"
+                                :type :numeric
+                                :value 66
+                                :description "test description"
+                                :modified-at now)]
+
+    (t/is (= (:name token1) "test-token-1"))
+    (t/is (= (:type token1) :boolean))
+    (t/is (= (:value token1) true))
+    (t/is (nil? (:description token1)))
+    (t/is (some? (:modified-at token1)))
+    (t/is (ctob/valid-token? token1))
+
+    (t/is (= (:name token2) "test-token-2"))
+    (t/is (= (:type token2) :numeric))
+    (t/is (= (:value token2) 66))
+    (t/is (= (:description token2) "test description"))
+    (t/is (= (:modified-at token2) now))
+    (t/is (ctob/valid-token? token2))))
+
+(t/deftest invalid-tokens
+  (let [args {:name 777
+              :type :invalid}]
+    (t/is (thrown-with-msg? Exception #"expected valid token"
+                            (apply ctob/make-token args)))
+    (t/is (false? (ctob/valid-token? {})))))
+
+(t/deftest make-token-set
+  (let [now        (dt/now)
+        token-set1 (ctob/make-token-set :name "test-token-set-1")
+        token-set2 (ctob/make-token-set :name "test-token-set-2"
+                                        :description "test description"
+                                        :modified-at now
+                                        :tokens [])]
+
+    (t/is (= (:name token-set1) "test-token-set-1"))
+    (t/is (nil? (:description token-set1)))
+    (t/is (some? (:modified-at token-set1)))
+    (t/is (empty? (:tokens token-set1)))
+
+    (t/is (= (:name token-set2) "test-token-set-2"))
+    (t/is (= (:description token-set2) "test description"))
+    (t/is (= (:modified-at token-set2) now))
+    (t/is (empty? (:tokens token-set2)))))
+
+(t/deftest invalid-token-set
+  (let [args {:name 777
+              :description 999}]
+    (t/is (thrown-with-msg? Exception #"expected valid token set"
+                            (apply ctob/make-token-set args)))))
+
+(t/deftest make-tokens-lib
+  (let [tokens-lib (ctob/make-tokens-lib)]
+    (t/is (= (ctob/set-count tokens-lib) 0))))
+
+(t/deftest add-token-set
+  (let [tokens-lib  (ctob/make-tokens-lib)
+        token-set   (ctob/make-token-set :name "test-token-set")
+        tokens-lib' (ctob/add-set tokens-lib token-set)
+
+        token-sets' (ctob/get-sets tokens-lib')
+        token-set'  (ctob/get-set tokens-lib' "test-token-set")]
+
+    (t/is (= (ctob/set-count tokens-lib') 1))
+    (t/is (= (first token-sets') token-set))
+    (t/is (= token-set' token-set))))
+
+(t/deftest update-token-set
+  (let [tokens-lib  (-> (ctob/make-tokens-lib)
+                        (ctob/add-set (ctob/make-token-set :name "test-token-set")))
+
+        tokens-lib' (-> tokens-lib
+                        (ctob/update-set "test-token-set"
+                                         (fn [token-set]
+                                           (assoc token-set
+                                                  :name "updated-name"
+                                                  :description "some description")))
+                        (ctob/update-set "not-existing-set"
+                                         (fn [token-set]
+                                           (assoc token-set
+                                                  :name "no-effect"))))
+
+        token-set   (ctob/get-set tokens-lib "test-token-set")
+        token-set'  (ctob/get-set tokens-lib' "updated-name")]
+
+    (t/is (= (ctob/set-count tokens-lib') 1))
+    (t/is (= (:name token-set') "updated-name"))
+    (t/is (= (:description token-set') "some description"))
+    (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+
+(t/deftest delete-token-set
+  (let [tokens-lib  (-> (ctob/make-tokens-lib)
+                        (ctob/add-set (ctob/make-token-set :name "test-token-set")))
+
+        tokens-lib' (-> tokens-lib
+                        (ctob/delete-set "test-token-set")
+                        (ctob/delete-set "not-existing-set"))
+
+        token-set'  (ctob/get-set tokens-lib' "updated-name")]
+
+    (t/is (= (ctob/set-count tokens-lib') 0))
+    (t/is (nil? token-set'))))
+
+(t/deftest add-token
+  (let [tokens-lib  (-> (ctob/make-tokens-lib)
+                        (ctob/add-set (ctob/make-token-set :name "test-token-set")))
+        token       (ctob/make-token :name "test-token"
+                                     :type :boolean
+                                     :value true)
+        tokens-lib' (-> tokens-lib
+                        (ctob/add-token-in-set "test-token-set" token)
+                        (ctob/add-token-in-set "not-existing-set" token))
+
+        token-set   (ctob/get-set tokens-lib "test-token-set")
+        token-set'  (ctob/get-set tokens-lib' "test-token-set")
+        token'      (get-in token-set' [:tokens "test-token"])]
+
+    (t/is (= (ctob/set-count tokens-lib') 1))
+    (t/is (= (count (:tokens token-set')) 1))
+    (t/is (= (:name token') "test-token"))
+    (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+
+(t/deftest update-token
+  (let [tokens-lib  (-> (ctob/make-tokens-lib)
+                        (ctob/add-set (ctob/make-token-set :name "test-token-set"))
+                        (ctob/add-token-in-set "test-token-set"
+                                               (ctob/make-token :name "test-token"
+                                                                :type :boolean
+                                                                :value true)))
+
+        tokens-lib' (-> tokens-lib
+                        (ctob/update-token-in-set "test-token-set" "test-token"
+                                                  (fn [token]
+                                                    (assoc token
+                                                           :name "updated-name"
+                                                           :description "some description"
+                                                           :value false)))
+                        (ctob/update-token-in-set "not-existing-set" "test-token"
+                                                  (fn [token]
+                                                    (assoc token
+                                                           :name "no-effect")))
+                        (ctob/update-token-in-set "test-token-set" "not-existing-token"
+                                                  (fn [token]
+                                                    (assoc token
+                                                           :name "no-effect"))))
+
+        token-set   (ctob/get-set tokens-lib "test-token-set")
+        token-set'  (ctob/get-set tokens-lib' "test-token-set")
+        token       (get-in token-set [:tokens "test-token"])
+        token'      (get-in token-set' [:tokens "updated-name"])]
+
+    (t/is (= (ctob/set-count tokens-lib') 1))
+    (t/is (= (count (:tokens token-set')) 1))
+    (t/is (= (:name token') "updated-name"))
+    (t/is (= (:description token') "some description"))
+    (t/is (= (:value token') false))
+    (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))
+    (t/is (dt/is-after? (:modified-at token') (:modified-at token)))))
+
+(t/deftest delete-token
+  (let [tokens-lib  (-> (ctob/make-tokens-lib)
+                        (ctob/add-set (ctob/make-token-set :name "test-token-set"))
+                        (ctob/add-token-in-set "test-token-set"
+                                               (ctob/make-token :name "test-token"
+                                                                :type :boolean
+                                                                :value true)))
+        tokens-lib' (-> tokens-lib
+                        (ctob/delete-token-from-set "test-token-set" "test-token")
+                        (ctob/delete-token-from-set "not-existing-set" "test-token")
+                        (ctob/delete-token-from-set "test-set" "not-existing-token"))
+
+        token-set   (ctob/get-set tokens-lib "test-token-set")
+        token-set'  (ctob/get-set tokens-lib' "test-token-set")
+        token'      (get-in token-set' [:tokens "test-token"])]
+
+    (t/is (= (ctob/set-count tokens-lib') 1))
+    (t/is (= (count (:tokens token-set')) 0))
+    (t/is (nil? token'))
+    (t/is (dt/is-after? (:modified-at token-set') (:modified-at token-set)))))
+
+(t/deftest transit-serialization
+  (let [tokens-lib  (-> (ctob/make-tokens-lib)
+                        (ctob/add-set (ctob/make-token-set :name "test-token-set"))
+                        (ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token"
+                                                                                 :type :boolean
+                                                                                 :value true)))
+        encoded-str (tr/encode-str tokens-lib)
+        tokens-lib' (tr/decode-str encoded-str)]
+
+    (t/is (ctob/valid-tokens-lib? tokens-lib'))
+    (t/is (= (ctob/set-count tokens-lib') 1))))
+
+(t/deftest fressian-serialization
+  (let [tokens-lib   (-> (ctob/make-tokens-lib)
+                         (ctob/add-set (ctob/make-token-set :name "test-token-set"))
+                         (ctob/add-token-in-set "test-token-set" (ctob/make-token :name "test-token"
+                                                                                  :type :boolean
+                                                                                  :value true)))
+        encoded-blob (fres/encode tokens-lib)
+        tokens-lib'  (fres/decode encoded-blob)]
+
+    (t/is (ctob/valid-tokens-lib? tokens-lib'))
+    (t/is (= (ctob/set-count tokens-lib') 1))))
diff --git a/frontend/package.json b/frontend/package.json
index 3d81cc512..c7d620fc8 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -96,6 +96,7 @@
   },
   "dependencies": {
     "@tokens-studio/sd-transforms": "^0.16.1",
+    "bun": "^1.1.25",
     "compression": "^1.7.4",
     "date-fns": "^3.6.0",
     "eventsource-parser": "^1.1.2",
diff --git a/frontend/src/app/main/data/tokens.cljs b/frontend/src/app/main/data/tokens.cljs
index e13527db0..637b899b2 100644
--- a/frontend/src/app/main/data/tokens.cljs
+++ b/frontend/src/app/main/data/tokens.cljs
@@ -81,6 +81,11 @@
   (let [workspace-data (deref refs/workspace-data)]
     (get (:tokens workspace-data) id)))
 
+(defn get-token-set-data-from-token-set-id
+  [id]
+  (let [workspace-data (deref refs/workspace-data)]
+    (get (:token-sets-index workspace-data) id)))
+
 (defn set-selected-token-set-id
   [id]
   (ptk/reify ::set-selected-token-set-id
@@ -88,6 +93,10 @@
     (update [_ state]
       (wtts/assoc-selected-token-set-id state id))))
 
+(defn get-token-set-tokens
+  [token-set file]
+  (map #(get-in file [:tokens %]) (:tokens token-set)))
+
 (defn create-token-theme [token-theme]
   (let [new-token-theme (merge
                          {:id (uuid/next)
@@ -120,7 +129,7 @@
      (not theme-id) (-> changes
                         (pcb/add-temporary-token-theme
                          {:id (uuid/next)
-                          :name ""
+                          :name "Test theme"
                           :sets #{id}}))
      new-set? (-> changes
                   (pcb/update-token-theme
@@ -196,14 +205,14 @@
          (dch/commit-changes changes)
          (wtu/update-workspace-tokens))))))
 
-(defn delete-token-set [token-set-id]
+(defn delete-token-set [token-set-id token-set-name]
   (ptk/reify ::delete-token-set
     ptk/WatchEvent
     (watch [it state _]
       (let [data (get state :workspace-data)
             changes (-> (pcb/empty-changes it)
                         (pcb/with-library-data data)
-                        (pcb/delete-token-set token-set-id))]
+                        (pcb/delete-token-set token-set-id token-set-name))]
         (rx/of
          (dch/commit-changes changes)
          (wtu/update-workspace-tokens))))))
@@ -214,46 +223,43 @@
     (ptk/reify ::update-create-token
       ptk/WatchEvent
       (watch [it state _]
-        (let [prev-token (get-token-data-from-token-id (:id token))
+        (let [token-set     (wtts/get-selected-token-set state)
+              create-set?   (not token-set)
+              token-set     (or token-set
+                                {:id (uuid/next)
+                                 :name "Global"
+                                 :tokens []})
+
+              changes       (cond-> (pcb/empty-changes it)
+                              create-set?
+                              (pcb/add-token-set token-set))
+
+              prev-token    (d/seek #(= (:id %) (:id token)) (:tokens token-set))   ; TODO
               create-token? (not prev-token)
-              token-changes (if create-token?
-                              (-> (pcb/empty-changes it)
-                                  (pcb/add-token token))
-                              (-> (pcb/empty-changes it)
-                                  (pcb/update-token token prev-token)))
-              token-set (wtts/get-selected-token-set state)
-              create-set? (not token-set)
-              new-token-set {:id (uuid/next)
-                             :name "Global"
-                             :tokens [(:id token)]}
-              selected-token-set-id (if create-set?
-                                      (:id new-token-set)
-                                      (:id token-set))
-              set-changes (cond
-                            create-set? (-> token-changes
-                                            (pcb/add-token-set new-token-set))
-                            :else (let [updated-token-set (if (contains? token-set (:id token))
-                                                            token-set
-                                                            (update token-set :tokens conj (:id token)))]
-                                    (-> token-changes
-                                        (pcb/update-token-set updated-token-set token-set))))
-              theme-changes (-> set-changes
+
+              changes       (if create-token?
+                              (pcb/add-token changes (:id token-set) (:name token-set) token)
+                              (pcb/update-token changes (:id token-set) (:name token-set) token prev-token))
+
+              changes       (-> changes
                                 (ensure-token-theme-changes state {:new-set? create-set?
-                                                                   :id selected-token-set-id}))]
+                                                                   :id (:id token-set)}))]
           (rx/of
-           (set-selected-token-set-id selected-token-set-id)
-           (dch/commit-changes theme-changes)))))))
+           (set-selected-token-set-id (:id token-set))
+           (dch/commit-changes changes)))))))
 
 (defn delete-token
-  [id]
+  [set-name id name]
+  (dm/assert! (string? set-name))
   (dm/assert! (uuid? id))
+  (dm/assert! (string? name))
   (ptk/reify ::delete-token
     ptk/WatchEvent
     (watch [it state _]
       (let [data    (get state :workspace-data)
             changes (-> (pcb/empty-changes it)
                         (pcb/with-library-data data)
-                        (pcb/delete-token id))]
+                        (pcb/delete-token set-name id name))]
         (rx/of (dch/commit-changes changes))))))
 
 (defn duplicate-token
diff --git a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs
index 56c392f3c..1eba57205 100644
--- a/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/context_menu.cljs
@@ -201,10 +201,11 @@
                     (generic-attribute-actions #{:x} "X" (assoc context-data :on-update-shape wtch/update-shape-position))
                     (generic-attribute-actions #{:y} "Y" (assoc context-data :on-update-shape wtch/update-shape-position))))}))
 
-(defn default-actions [{:keys [token]}]
-  (let [{:keys [modal]} (wtty/get-token-properties token)]
+(defn default-actions [{:keys [token selected-token-set-id]}]
+  (let [{:keys [modal]} (wtty/get-token-properties token)
+        selected-token-set (dt/get-token-set-data-from-token-set-id selected-token-set-id)]
     [{:title "Delete Token"
-      :action #(st/emit! (dt/delete-token (:id token)))}
+      :action #(st/emit! (dt/delete-token (:name selected-token-set) (:id token) (:name token)))}
      {:title "Duplicate Token"
       :action #(st/emit! (dt/duplicate-token (:id token)))}
      {:title "Edit Token"
@@ -311,7 +312,8 @@
         selected (mf/deref refs/selected-shapes)
         selected-shapes (into [] (keep (d/getf objects)) selected)
         token-id (:token-id mdata)
-        token (get (mf/deref refs/workspace-selected-token-set-tokens) token-id)]
+        token (get (mf/deref refs/workspace-selected-token-set-tokens) token-id)
+        selected-token-set-id (mf/deref refs/workspace-selected-token-set-id)]
     (mf/use-effect
      (mf/deps mdata)
      (fn []
@@ -327,4 +329,5 @@
         [:ul {:class (stl/css :context-list)}
          [:& menu-tree {:submenu-offset @width
                         :token token
+                        :selected-token-set-id selected-token-set-id
                         :selected-shapes selected-shapes}]])]]))
diff --git a/frontend/src/app/main/ui/workspace/tokens/sets.cljs b/frontend/src/app/main/ui/workspace/tokens/sets.cljs
index 184c38cce..358a9e53f 100644
--- a/frontend/src/app/main/ui/workspace/tokens/sets.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/sets.cljs
@@ -26,9 +26,9 @@
 (defn on-select-token-set-click [id]
   (st/emit! (wdt/set-selected-token-set-id id)))
 
-(defn on-delete-token-set-click [id event]
+(defn on-delete-token-set-click [id name event]
   (dom/stop-propagation event)
-  (st/emit! (wdt/delete-token-set id)))
+  (st/emit! (wdt/delete-token-set id name)))
 
 (defn on-update-token-set [token-set]
   (st/emit! (wdt/update-token-set token-set)))
@@ -104,7 +104,7 @@
         [:*
          [:div {:class (stl/css :set-name)} name]
          [:div {:class (stl/css :delete-set)}
-          [:button {:on-click #(on-delete-token-set-click id %)
+          [:button {:on-click #(on-delete-token-set-click id name %)
                     :type "button"}
            i/delete]]
          (if set?
diff --git a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs
index a083bed54..2faadab69 100644
--- a/frontend/src/app/main/ui/workspace/tokens/token_types.cljs
+++ b/frontend/src/app/main/ui/workspace/tokens/token_types.cljs
@@ -13,106 +13,65 @@
 
 (def token-types
   (ordered-map
-   [:border-radius
-    {:title "Border Radius"
-     :attributes ctt/border-radius-keys
-     :on-update-shape wtch/update-shape-radius-all
-     :modal {:key :tokens/border-radius
-             :fields [{:label "Border Radius"
-                       :key :border-radius}]}}]
-   [:stroke-width
-    {:title "Stroke Width"
-     :attributes ctt/stroke-width-keys
-     :on-update-shape wtch/update-stroke-width
-     :modal {:key :tokens/stroke-width
-             :fields [{:label "Stroke Width"
-                       :key :stroke-width}]}}]
+   :border-radius
+   {:title "Border Radius"
+    :attributes ctt/border-radius-keys
+    :on-update-shape wtch/update-shape-radius-all
+    :modal {:key :tokens/border-radius
+            :fields [{:label "Border Radius"
+                      :key :border-radius}]}}
+   :stroke-width
+   {:title "Stroke Width"
+    :attributes ctt/stroke-width-keys
+    :on-update-shape wtch/update-stroke-width
+    :modal {:key :tokens/stroke-width
+            :fields [{:label "Stroke Width"
+                      :key :stroke-width}]}}
 
-   [:sizing
-    {:title "Sizing"
-     :attributes #{:width :height}
-     :all-attributes ctt/sizing-keys
-     :on-update-shape wtch/update-shape-dimensions
-     :modal {:key :tokens/sizing
-             :fields [{:label "Sizing"
-                       :key :sizing}]}}]
-   [:dimensions
-    {:title "Dimensions"
-     :attributes #{:width :height}
-     :all-attributes (set/union
-                      ctt/spacing-keys
-                      ctt/sizing-keys
-                      ctt/border-radius-keys
-                      ctt/stroke-width-keys)
-     :on-update-shape wtch/update-shape-dimensions
-     :modal {:key :tokens/dimensions
-             :fields [{:label "Dimensions"
-                       :key :dimensions}]}}]
+   :sizing
+   {:title "Sizing"
+    :attributes #{:width :height}
+    :all-attributes ctt/sizing-keys
+    :on-update-shape wtch/update-shape-dimensions
+    :modal {:key :tokens/sizing
+            :fields [{:label "Sizing"
+                      :key :sizing}]}}
+   :dimensions
+   {:title "Dimensions"
+    :attributes #{:width :height}
+    :all-attributes (set/union
+                     ctt/spacing-keys
+                     ctt/sizing-keys
+                     ctt/border-radius-keys
+                     ctt/stroke-width-keys)
+    :on-update-shape wtch/update-shape-dimensions
+    :modal {:key :tokens/dimensions
+            :fields [{:label "Dimensions"
+                      :key :dimensions}]}}
 
-   [:opacity
-    {:title "Opacity"
-     :attributes ctt/opacity-keys
-     :on-update-shape wtch/update-opacity
-     :modal {:key :tokens/opacity
-             :fields [{:label "Opacity"
-                       :key :opacity}]}}]
+   :opacity
+   {:title "Opacity"
+    :attributes ctt/opacity-keys
+    :on-update-shape wtch/update-opacity
+    :modal {:key :tokens/opacity
+            :fields [{:label "Opacity"
+                      :key :opacity}]}}
 
-   [:rotation
-    {:title "Rotation"
-     :attributes ctt/rotation-keys
-     :on-update-shape wtch/update-rotation
-     :modal {:key :tokens/rotation
-             :fields [{:label "Rotation"
-                       :key :rotation}]}}]
-   [:spacing
-    {:title "Spacing"
-     :attributes #{:column-gap :row-gap}
-     :all-attributes ctt/spacing-keys
-     :on-update-shape wtch/update-layout-spacing
-     :modal {:key :tokens/spacing
-             :fields [{:label "Spacing"
-                       :key :spacing}]}}]
-   (comment
-     [:boolean
-      {:title "Boolean"
-       :modal {:key :tokens/boolean
-               :fields [{:label "Boolean"}]}}]
-
-     [:box-shadow
-      {:title "Box Shadow"
-       :modal {:key :tokens/box-shadow
-               :fields [{:label "Box shadows"
-                         :key :box-shadow
-                         :type :box-shadow}]}}]
-
-     [:numeric
-      {:title "Numeric"
-       :modal {:key :tokens/numeric
-               :fields [{:label "Numeric"
-                         :key :numeric}]}}]
-
-     [:other
-      {:title "Other"
-       :modal {:key :tokens/other
-               :fields [{:label "Other"
-                         :key :other}]}}]
-     [:string
-      {:title "String"
-       :modal {:key :tokens/string
-               :fields [{:label "String"
-                         :key :string}]}}]
-     [:typography
-      {:title "Typography"
-       :modal {:key :tokens/typography
-               :fields [{:label "Font" :key :font-family}
-                        {:label "Weight" :key :weight}
-                        {:label "Font Size" :key :font-size}
-                        {:label "Line Height" :key :line-height}
-                        {:label "Letter Spacing" :key :letter-spacing}
-                        {:label "Paragraph Spacing" :key :paragraph-spacing}
-                        {:label "Paragraph Indent" :key :paragraph-indent}
-                        {:label "Text Decoration" :key :text-decoration}
-                        {:label "Text Case" :key :text-case}]}}])))
+   :rotation
+   {:title "Rotation"
+    :attributes ctt/rotation-keys
+    :on-update-shape wtch/update-rotation
+    :modal {:key :tokens/rotation
+            :fields [{:label "Rotation"
+                      :key :rotation}]}}
+   :spacing
+   {:title "Spacing"
+    :attributes #{:column-gap :row-gap}
+    :all-attributes ctt/spacing-keys
+    :on-update-shape wtch/update-layout-spacing
+    :modal {:key :tokens/spacing
+            :fields [{:label "Spacing"
+                      :key :spacing}]}}))
 
 (defn get-token-properties [token]
   (get token-types (:type token)))
diff --git a/frontend/test/token_tests/logic/token_actions_test.cljs b/frontend/test/token_tests/logic/token_actions_test.cljs
index 0fb1ea5ec..c95af99a2 100644
--- a/frontend/test/token_tests/logic/token_actions_test.cljs
+++ b/frontend/test/token_tests/logic/token_actions_test.cljs
@@ -1,5 +1,6 @@
 (ns token-tests.logic.token-actions-test
   (:require
+   [app.common.pprint :refer [pprint]]
    [app.common.logging :as log]
    [app.common.test-helpers.compositions :as ctho]
    [app.common.test-helpers.files :as cthf]
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 9fe74c9f0..ebde9b8cf 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -2427,6 +2427,62 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@oven/bun-darwin-aarch64@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-darwin-aarch64@npm:1.1.25"
+  conditions: os=darwin & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-darwin-x64-baseline@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-darwin-x64-baseline@npm:1.1.25"
+  conditions: os=darwin & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-darwin-x64@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-darwin-x64@npm:1.1.25"
+  conditions: os=darwin & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-linux-aarch64@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-linux-aarch64@npm:1.1.25"
+  conditions: os=linux & cpu=arm64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-linux-x64-baseline@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-linux-x64-baseline@npm:1.1.25"
+  conditions: os=linux & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-linux-x64@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-linux-x64@npm:1.1.25"
+  conditions: os=linux & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-windows-x64-baseline@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-windows-x64-baseline@npm:1.1.25"
+  conditions: os=win32 & cpu=x64
+  languageName: node
+  linkType: hard
+
+"@oven/bun-windows-x64@npm:1.1.25":
+  version: 1.1.25
+  resolution: "@oven/bun-windows-x64@npm:1.1.25"
+  conditions: os=win32 & cpu=x64
+  languageName: node
+  linkType: hard
+
 "@pkgjs/parseargs@npm:^0.11.0":
   version: 0.11.0
   resolution: "@pkgjs/parseargs@npm:0.11.0"
@@ -4379,6 +4435,43 @@ __metadata:
   languageName: node
   linkType: hard
 
+"bun@npm:^1.1.25":
+  version: 1.1.25
+  resolution: "bun@npm:1.1.25"
+  dependencies:
+    "@oven/bun-darwin-aarch64": "npm:1.1.25"
+    "@oven/bun-darwin-x64": "npm:1.1.25"
+    "@oven/bun-darwin-x64-baseline": "npm:1.1.25"
+    "@oven/bun-linux-aarch64": "npm:1.1.25"
+    "@oven/bun-linux-x64": "npm:1.1.25"
+    "@oven/bun-linux-x64-baseline": "npm:1.1.25"
+    "@oven/bun-windows-x64": "npm:1.1.25"
+    "@oven/bun-windows-x64-baseline": "npm:1.1.25"
+  dependenciesMeta:
+    "@oven/bun-darwin-aarch64":
+      optional: true
+    "@oven/bun-darwin-x64":
+      optional: true
+    "@oven/bun-darwin-x64-baseline":
+      optional: true
+    "@oven/bun-linux-aarch64":
+      optional: true
+    "@oven/bun-linux-x64":
+      optional: true
+    "@oven/bun-linux-x64-baseline":
+      optional: true
+    "@oven/bun-windows-x64":
+      optional: true
+    "@oven/bun-windows-x64-baseline":
+      optional: true
+  bin:
+    bun: bin/bun.exe
+    bunx: bin/bun.exe
+  checksum: 10c0/ac0cec14aaaaae8b5b3818c6868dd417a60fb5d9c4230f5265c19c0367fd588ae9175ce0feac04db5b5684d624187c639fae1854ee2b52cdd2e2ae0a75a7dd9c
+  conditions: (os=darwin | os=linux | os=win32) & (cpu=arm64 | cpu=x64)
+  languageName: node
+  linkType: hard
+
 "bytes@npm:3.0.0":
   version: 3.0.0
   resolution: "bytes@npm:3.0.0"
@@ -6819,6 +6912,7 @@ __metadata:
     "@tokens-studio/sd-transforms": "npm:^0.16.1"
     "@types/node": "npm:^20.11.20"
     autoprefixer: "npm:^10.4.19"
+    bun: "npm:^1.1.25"
     compression: "npm:^1.7.4"
     concurrently: "npm:^8.2.2"
     date-fns: "npm:^3.6.0"