+```
+
+Keep this narrow; do not refactor unrelated styles.
+
+- [ ] **Step 3: Extend the create modal with a hex color input**
+
+Add a `Couleur` field to `CreateTabloModalBody` using the shared form primitives, with placeholder/example text like `#3B82F6`.
+
+- [ ] **Step 4: Add the edit modal with both controls**
+
+Add an edit modal body that renders:
+
+- `Nom du projet`
+- a hex text input
+- a native color picker input
+
+Use the same `vm.FormColor` source for both values.
+
+Representative shape:
+
+```templ
+@ui.FormField(ui.FormFieldProps{
+ Label: "Couleur",
+ For: "tablo-color",
+ Field: templ.ComponentFunc(func(ctx context.Context, w io.Writer) error {
+ return templ.Join(
+ ui.Input(ui.InputProps{
+ ID: "tablo-color",
+ Name: "color",
+ Value: vm.FormColor,
+ Placeholder: "#3B82F6",
+ Type: "text",
+ }),
+ templ.Raw(`
`),
+ ).Render(ctx, w)
+ }),
+})
+```
+
+If inline JS is undesirable inside templ, use the smallest equivalent attribute approach already accepted by the codebase. Keep the submission source canonical: the posted `color` field must still carry the validated hex value.
+
+- [ ] **Step 5: Switch modal rendering by modal kind and regenerate templ**
+
+Update `TablosPageContent` so it renders:
+
+- create modal when modal kind is `create`
+- edit modal when modal kind is `edit`
+
+Run: `cd go-backend && just generate`
+
+Expected: PASS with regenerated `go-backend/internal/web/views/tablos_templ.go`.
+
+- [ ] **Step 6: Run the focused handler suite**
+
+Run: `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`
+
+Expected: PASS.
+
+- [ ] **Step 7: Run the full Go backend suite**
+
+Run: `cd go-backend && go test ./...`
+
+Expected: PASS.
+
+- [ ] **Step 8: Commit the UI and final verification**
+
+```bash
+git add go-backend/internal/web/views/tablos.templ go-backend/internal/web/views/tablos_templ.go go-backend/internal/web/handlers/tablos_test.go
+git commit -m "feat: add go-backend tablo edit modal"
+```
+
+## Final Verification Checklist
+
+- [ ] `cd go-backend && just generate`
+- [ ] `cd go-backend && go test ./internal/web/handlers -run 'Tablos|InMemoryTablos' -v`
+- [ ] `cd go-backend && go test ./...`
+- [ ] Manually verify in the browser via `cd go-backend && just dev`:
+ - create modal requires a valid hex color
+ - grid cards show edit to the left of delete
+ - list rows show edit to the left of delete
+ - edit modal preloads current name and color
+ - edit modal shows both hex input and color picker
+ - changing the picker updates the submitted hex value
+ - successful save refreshes `/tablos` and closes the modal
+
+Plan complete and saved to `docs/superpowers/plans/2026-05-10-go-backend-tablo-edit-color.md`. Ready to execute?
diff --git a/go-backend/internal/web/ui/space.templ b/go-backend/internal/web/ui/space.templ
new file mode 100644
index 0000000..0c97414
--- /dev/null
+++ b/go-backend/internal/web/ui/space.templ
@@ -0,0 +1,13 @@
+package ui
+
+type SpaceProps struct {
+ Size SpacingStep
+}
+
+templ SpaceX(props SpaceProps) {
+
+}
+
+templ SpaceY(props SpaceProps) {
+
+}
diff --git a/go-backend/internal/web/ui/space_templ.go b/go-backend/internal/web/ui/space_templ.go
new file mode 100644
index 0000000..dde7e27
--- /dev/null
+++ b/go-backend/internal/web/ui/space_templ.go
@@ -0,0 +1,109 @@
+// Code generated by templ - DO NOT EDIT.
+
+// templ: version: v0.3.1001
+package ui
+
+//lint:file-ignore SA4006 This context is only used if a nested component is present.
+
+import "github.com/a-h/templ"
+import templruntime "github.com/a-h/templ/runtime"
+
+type SpaceProps struct {
+ Size SpacingStep
+}
+
+func SpaceX(props SpaceProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var1 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var1 == nil {
+ templ_7745c5c3_Var1 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var2 = []any{spaceXClass(props.Size)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+func SpaceY(props SpaceProps) templ.Component {
+ return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
+ templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
+ if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
+ return templ_7745c5c3_CtxErr
+ }
+ templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
+ if !templ_7745c5c3_IsBuffer {
+ defer func() {
+ templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
+ if templ_7745c5c3_Err == nil {
+ templ_7745c5c3_Err = templ_7745c5c3_BufErr
+ }
+ }()
+ }
+ ctx = templ.InitializeContext(ctx)
+ templ_7745c5c3_Var4 := templ.GetChildren(ctx)
+ if templ_7745c5c3_Var4 == nil {
+ templ_7745c5c3_Var4 = templ.NopComponent
+ }
+ ctx = templ.ClearChildren(ctx)
+ var templ_7745c5c3_Var5 = []any{spaceYClass(props.Size)}
+ templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var5...)
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
")
+ if templ_7745c5c3_Err != nil {
+ return templ_7745c5c3_Err
+ }
+ return nil
+ })
+}
+
+var _ = templruntime.GeneratedTemplate
diff --git a/go-backend/internal/web/views/dashboard_components_test.go b/go-backend/internal/web/views/dashboard_components_test.go
new file mode 100644
index 0000000..57bc770
--- /dev/null
+++ b/go-backend/internal/web/views/dashboard_components_test.go
@@ -0,0 +1,91 @@
+package views
+
+import (
+ "bytes"
+ "context"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/a-h/templ"
+ "github.com/google/uuid"
+ tablomodel "xtablo-backend/internal/tablos"
+)
+
+func TestOverviewProjectsFromTablosCarriesColorAndEditURL(t *testing.T) {
+ record := tablomodel.Record{
+ ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
+ Name: "Palette",
+ Color: "#3B82F6",
+ Status: tablomodel.StatusTodo,
+ CreatedAt: time.Date(2026, time.May, 10, 9, 0, 0, 0, time.UTC),
+ }
+
+ projects := OverviewProjectsFromTablos([]tablomodel.Record{record})
+ if len(projects) != 1 {
+ t.Fatalf("expected one project, got %d", len(projects))
+ }
+
+ project := projects[0]
+ if project.Color != "#3B82F6" {
+ t.Fatalf("expected color to be preserved, got %q", project.Color)
+ }
+ if project.EditRequestURL != "/tablos/11111111-1111-1111-1111-111111111111/edit" {
+ t.Fatalf("expected edit request url to be set, got %q", project.EditRequestURL)
+ }
+}
+
+func TestOverviewProjectsSectionRendersColorAndEditAction(t *testing.T) {
+ record := tablomodel.Record{
+ ID: uuid.MustParse("11111111-1111-1111-1111-111111111111"),
+ Name: "Palette",
+ Color: "#3B82F6",
+ Status: tablomodel.StatusTodo,
+ CreatedAt: time.Date(2026, time.May, 10, 9, 0, 0, 0, time.UTC),
+ }
+
+ html := renderViewToString(t, OverviewProjectsSection(OverviewProjectsFromTablos([]tablomodel.Record{record})))
+
+ for _, want := range []string{
+ `style="--project-color:#3B82F6;"`,
+ `aria-label="Modifier le projet"`,
+ `hx-get="/tablos/11111111-1111-1111-1111-111111111111/edit"`,
+ } {
+ if !strings.Contains(html, want) {
+ t.Fatalf("expected %q in %q", want, html)
+ }
+ }
+}
+
+func TestTabloListRowDoesNotRenderSpacerBetweenEditAndDelete(t *testing.T) {
+ component := TabloListRow(TabloCardView{
+ ID: "11111111-1111-1111-1111-111111111111",
+ Name: "Palette",
+ Color: "#3B82F6",
+ StatusLabel: "À faire",
+ StatusTone: "info",
+ Progress: 0,
+ ProgressLabel: "0%",
+ CreatedAtLabel: "10 mai 2026",
+ DeleteRequestURL: "/tablos/11111111-1111-1111-1111-111111111111",
+ EditRequestURL: "/tablos/11111111-1111-1111-1111-111111111111/edit",
+ IconKind: "bolt",
+ Initial: "P",
+ })
+
+ html := renderViewToString(t, component)
+
+ if strings.Contains(html, `ui-space-x`) {
+ t.Fatalf("expected no spacer markup in list row actions, got %q", html)
+ }
+}
+
+func renderViewToString(t *testing.T, component templ.Component) string {
+ t.Helper()
+
+ var buf bytes.Buffer
+ if err := component.Render(context.Background(), &buf); err != nil {
+ t.Fatalf("render component: %v", err)
+ }
+ return buf.String()
+}