diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9568ed36..f3fbce39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,4 @@
+- Fixed Accept/Decline and Yes/No button colors to respect the IDE Look and Feel theme (dark themes no longer show clashing hardcoded colors)
- Added session management when opening a session from the toolbar and when closing a tab via the Close button
- Added configurable session mode: choose to resume the last session or start a new one
diff --git a/pom.xml b/pom.xml
index ad81952d..c6e262f8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.github.nbclaudecodegui
netbeans-claude-code-gui
- 0.21.12-SNAPSHOT
+ 0.21.14-SNAPSHOT
nbm
Netbeans Claude Code GUI
NetBeans plugin that provides a GUI for Claude Code CLI — run claude code sessions directly inside the IDE.
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanel.java b/src/main/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanel.java
index 39e18515..e381d929 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanel.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanel.java
@@ -2,6 +2,7 @@
import io.github.nbclaudecodegui.model.ChoiceMenuModel;
import io.github.nbclaudecodegui.ui.common.DecoratedTextField;
+import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.KeyEvent;
@@ -117,23 +118,10 @@ public void show(ChoiceMenuModel model, Consumer callback) {
String display = opt.display().trim();
String response = opt.response();
String label = display.equalsIgnoreCase("Yes")
- ? FileDiffPermissionPanel.ICON_ACCEPT + " " + display
- : FileDiffPermissionPanel.ICON_DECLINE + " " + display;
+ ? UiUtils.ICON_CHECK + " " + display
+ : UiUtils.ICON_CROSS + " " + display;
JButton btn = new JButton(label);
- Color baseColor = display.equalsIgnoreCase("Yes")
- ? new Color(34, 139, 34) : new Color(178, 34, 34);
- Color focusColor = baseColor.brighter();
- btn.setBackground(baseColor);
- btn.setForeground(Color.WHITE);
- btn.setOpaque(true);
- btn.addFocusListener(new java.awt.event.FocusAdapter() {
- @Override public void focusGained(java.awt.event.FocusEvent e) {
- btn.setBackground(focusColor);
- }
- @Override public void focusLost(java.awt.event.FocusEvent e) {
- btn.setBackground(baseColor);
- }
- });
+ UiUtils.applyActionStyle(btn, display.equalsIgnoreCase("Yes"));
btn.addActionListener(e -> {
LOG.info("[ChoiceMenuPanel] yes/no clicked: \"" + display + "\" → \"" + response + "\"");
submitAnswer(response);
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java b/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java
index e470f67d..cbbc76bb 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/ClaudePromptPanel.java
@@ -3,6 +3,7 @@
import io.github.nbclaudecodegui.settings.ClaudeCodePreferences;
import io.github.nbclaudecodegui.ui.common.AtPathHighlighter;
import io.github.nbclaudecodegui.ui.common.DecoratedTextArea;
+import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
@@ -134,8 +135,8 @@ public ClaudePromptPanel(Consumer onSend,
// Install @-path highlighter (DecoratedTextArea implements RangeHighlightable)
pathHighlighter = AtPathHighlighter.install(inputArea);
- sendButton = new JButton("" + ICON_SEND + " Send");
- cancelButton = new JButton("" + ICON_CANCEL + " Cancel");
+ sendButton = new JButton("" + ICON_SEND + " Send");
+ cancelButton = new JButton("" + ICON_CANCEL + " Cancel");
sendButton.setEnabled(false);
cancelButton.setEnabled(false);
sendButton.addActionListener(e -> doSend());
@@ -151,7 +152,7 @@ public ClaudePromptPanel(Consumer onSend,
buttonCol.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
JButton historyButton = new JButton("" + ICON_HISTORY + " History");
- JButton favoritesButton = new JButton("" + ICON_FAVORITES + " Favorites");
+ JButton favoritesButton = new JButton("" + ICON_FAVORITES + " Favorites");
historyButton.setAlignmentX(Component.CENTER_ALIGNMENT);
favoritesButton.setAlignmentX(Component.CENTER_ALIGNMENT);
historyButton.setToolTipText("Browse prompt history");
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/FileDiffPermissionPanel.java b/src/main/java/io/github/nbclaudecodegui/ui/FileDiffPermissionPanel.java
index 8c0b8075..d2a0ac27 100644
--- a/src/main/java/io/github/nbclaudecodegui/ui/FileDiffPermissionPanel.java
+++ b/src/main/java/io/github/nbclaudecodegui/ui/FileDiffPermissionPanel.java
@@ -1,6 +1,7 @@
package io.github.nbclaudecodegui.ui;
import io.github.nbclaudecodegui.ui.common.DecoratedTextField;
+import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.FlowLayout;
@@ -40,14 +41,8 @@ public final class FileDiffPermissionPanel extends JPanel {
/** Placeholder text shown in the decline reason field when it is empty. */
static final String REASON_HINT = "Decline reason (Optional)";
- /** \u2713 CHECK MARK — used on confirmation buttons (Accept, Yes) */
- public static final String ICON_ACCEPT = "\u2713";
-
/** Double check mark — used on AcceptAll button */
- private static final String ICON_ACCEPT_ALL = ICON_ACCEPT + ICON_ACCEPT;
-
- /** \u2717 BALLOT X — used on decline buttons (Decline, No) */
- public static final String ICON_DECLINE = "\u2717";
+ private static final String ICON_ACCEPT_ALL = UiUtils.ICON_CHECK + UiUtils.ICON_CHECK;
/**
* Creates the panel.
@@ -72,34 +67,14 @@ public FileDiffPermissionPanel(Runnable onAccept, Consumer onDecline,
acceptAllBtn.setEnabled(onAcceptAll != null);
// Accept button (green)
- JButton acceptBtn = new JButton(ICON_ACCEPT + " Accept");
+ JButton acceptBtn = new JButton(UiUtils.ICON_CHECK + " Accept");
acceptBtn.setToolTipText("Accept this change");
- acceptBtn.setOpaque(true);
- acceptBtn.setBackground(new Color(34, 139, 34));
- acceptBtn.setForeground(Color.WHITE);
- acceptBtn.addFocusListener(new java.awt.event.FocusAdapter() {
- @Override public void focusGained(java.awt.event.FocusEvent e) {
- acceptBtn.setBackground(new Color(34, 139, 34).brighter());
- }
- @Override public void focusLost(java.awt.event.FocusEvent e) {
- acceptBtn.setBackground(new Color(34, 139, 34));
- }
- });
+ UiUtils.applyActionStyle(acceptBtn, true);
// Decline button (red)
- JButton declineBtn = new JButton(ICON_DECLINE + " Decline");
+ JButton declineBtn = new JButton(UiUtils.ICON_CROSS + " Decline");
declineBtn.setToolTipText("Decline this change");
- declineBtn.setOpaque(true);
- declineBtn.setBackground(new Color(178, 34, 34));
- declineBtn.setForeground(Color.WHITE);
- declineBtn.addFocusListener(new java.awt.event.FocusAdapter() {
- @Override public void focusGained(java.awt.event.FocusEvent e) {
- declineBtn.setBackground(new Color(178, 34, 34).brighter());
- }
- @Override public void focusLost(java.awt.event.FocusEvent e) {
- declineBtn.setBackground(new Color(178, 34, 34));
- }
- });
+ UiUtils.applyActionStyle(declineBtn, false);
// Reason field
DecoratedTextField reasonField = new DecoratedTextField(() -> wdFinal);
diff --git a/src/main/java/io/github/nbclaudecodegui/ui/common/UiUtils.java b/src/main/java/io/github/nbclaudecodegui/ui/common/UiUtils.java
new file mode 100644
index 00000000..4f82903d
--- /dev/null
+++ b/src/main/java/io/github/nbclaudecodegui/ui/common/UiUtils.java
@@ -0,0 +1,79 @@
+package io.github.nbclaudecodegui.ui.common;
+
+import java.awt.Color;
+import javax.swing.JButton;
+import javax.swing.UIManager;
+
+/** Shared UI utility helpers for the plugin. */
+public final class UiUtils {
+
+ /** ✓ CHECK MARK — used on confirmation/positive action buttons. */
+ public static final String ICON_CHECK = "\u2713";
+
+ /** ✗ BALLOT X — used on decline/negative action buttons. */
+ public static final String ICON_CROSS = "\u2717";
+
+ private UiUtils() {}
+
+ /**
+ * Resolves a theme-aware color: tries the FlatLaf {@code UIManager} key first;
+ * falls back to {@code light} or {@code dark} based on the luminance of
+ * {@code Panel.background} (sum of RGB components < 384 → dark theme).
+ *
+ * @param flatLafKey UIManager key to try first (e.g. {@code "Actions.Green"})
+ * @param light color to use in light themes when the key is absent
+ * @param dark color to use in dark themes when the key is absent
+ * @return resolved {@link Color}, never {@code null}
+ */
+ public static Color resolveColor(String flatLafKey, Color light, Color dark) {
+ Color resolved = UIManager.getColor(flatLafKey);
+ if (resolved != null) return resolved;
+ Color panelBg = UIManager.getColor("Panel.background");
+ boolean isDark = panelBg != null
+ && (panelBg.getRed() + panelBg.getGreen() + panelBg.getBlue()) < 384;
+ return isDark ? dark : light;
+ }
+
+ /** Returns the theme-aware positive/confirm color (green). */
+ public static Color getPositiveColor() {
+ return resolveColor("Actions.Green", new Color(34, 139, 34), new Color(60, 160, 60));
+ }
+
+ /** Returns the theme-aware negative/cancel color (red). */
+ public static Color getNegativeColor() {
+ return resolveColor("Actions.Red", new Color(178, 34, 34), new Color(180, 60, 60));
+ }
+
+ /** Returns the theme-aware warning/accent color (orange). */
+ public static Color getWarningColor() {
+ return resolveColor("Actions.Yellow", new Color(180, 100, 0), new Color(200, 130, 0));
+ }
+
+ /**
+ * Formats a {@link Color} as an HTML hex string (e.g. {@code "#228B22"}).
+ * Useful for {@code } in HTML-rendered Swing labels/buttons.
+ *
+ * @param c the color to format
+ * @return six-digit uppercase hex string prefixed with {@code #}
+ */
+ public static String toHex(Color c) {
+ return String.format("#%02X%02X%02X", c.getRed(), c.getGreen(), c.getBlue());
+ }
+
+ /**
+ * Applies a theme-aware green ({@code positive=true}) or red ({@code positive=false})
+ * background style to a button: sets opaque background, white foreground, and a
+ * brighter focus highlight. Uses {@link #getPositiveColor()} / {@link #getNegativeColor()}.
+ */
+ public static void applyActionStyle(JButton btn, boolean positive) {
+ final Color base = positive ? getPositiveColor() : getNegativeColor();
+ final Color focus = base.brighter();
+ btn.setOpaque(true);
+ btn.setBackground(base);
+ btn.setForeground(Color.WHITE);
+ btn.addFocusListener(new java.awt.event.FocusAdapter() {
+ @Override public void focusGained(java.awt.event.FocusEvent e) { btn.setBackground(focus); }
+ @Override public void focusLost(java.awt.event.FocusEvent e) { btn.setBackground(base); }
+ });
+ }
+}
diff --git a/src/test/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanelTest.java b/src/test/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanelTest.java
index 949d4db8..a1804494 100644
--- a/src/test/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanelTest.java
+++ b/src/test/java/io/github/nbclaudecodegui/ui/ChoiceMenuPanelTest.java
@@ -2,6 +2,7 @@
import io.github.nbclaudecodegui.model.ChoiceMenuModel;
import io.github.nbclaudecodegui.model.ChoiceMenuModel.Option;
+import io.github.nbclaudecodegui.ui.common.UiUtils;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
@@ -44,8 +45,8 @@ void testShowWithOptionsCreatesButtons() throws Exception {
assertTrue(panel.getComponentCount() > 0, "panel should have components after show()");
List btnLabels = collectButtonLabels(panel);
- String yesLabel = FileDiffPermissionPanel.ICON_ACCEPT + " Yes";
- String noLabel = FileDiffPermissionPanel.ICON_DECLINE + " No";
+ String yesLabel = UiUtils.ICON_CHECK + " Yes";
+ String noLabel = UiUtils.ICON_CROSS + " No";
assertTrue(btnLabels.contains(yesLabel), "should have '" + yesLabel + "' button");
assertTrue(btnLabels.contains(noLabel), "should have '" + noLabel + "' button");
});
@@ -59,7 +60,7 @@ void testYesButtonIsGreen() throws Exception {
List.of(new Option("Yes", "1"), new Option("No", "2")), -1);
panel.show(model, answer -> {});
- JButton btn = findButton(panel, FileDiffPermissionPanel.ICON_ACCEPT + " Yes");
+ JButton btn = findButton(panel, UiUtils.ICON_CHECK + " Yes");
assertNotNull(btn, "Yes button must exist");
assertEquals(new Color(34, 139, 34), btn.getBackground(), "Yes button should be green");
});
@@ -73,7 +74,7 @@ void testNoButtonIsRed() throws Exception {
List.of(new Option("Yes", "1"), new Option("No", "2")), -1);
panel.show(model, answer -> {});
- JButton btn = findButton(panel, FileDiffPermissionPanel.ICON_DECLINE + " No");
+ JButton btn = findButton(panel, UiUtils.ICON_CROSS + " No");
assertNotNull(btn, "No button must exist");
assertEquals(new Color(178, 34, 34), btn.getBackground(), "No button should be red");
});
@@ -183,7 +184,7 @@ void testButtonClickInvokesCallbackWithResponse() throws Exception {
List.of(new Option("Yes", "1"), new Option("No", "2")), -1);
panel.show(model, received::add);
- JButton yesBtn = findButton(panel, FileDiffPermissionPanel.ICON_ACCEPT + " Yes");
+ JButton yesBtn = findButton(panel, UiUtils.ICON_CHECK + " Yes");
assertNotNull(yesBtn, "Yes button must exist");
yesBtn.doClick();
});
@@ -199,7 +200,7 @@ void testHideAfterButtonClick() throws Exception {
ChoiceMenuModel model = new ChoiceMenuModel("Go?", List.of(new Option("Yes", "1")), -1);
panel.show(model, answer -> {});
- JButton btn = findButton(panel, FileDiffPermissionPanel.ICON_ACCEPT + " Yes");
+ JButton btn = findButton(panel, UiUtils.ICON_CHECK + " Yes");
assertNotNull(btn);
btn.doClick();